Posts for: #2022/06

m1 chip macbook と cdk/aws-lambda は相性が悪い

0時に寝て6時に起きた。

m1 chip macbook で aws-lambda-python-alpha のデプロイができない

少し前に aws lambda の管理を serverless framework から cdk 移行した 。lambda 関数は python スクリプトで実装されているので @aws-cdk/aws-lambda-python-alpha を使っている。このライブラリでは python distribution を作るときの python インタープリターをローカルのものではなく docker イメージを使って管理しているようにみえる。私の環境 (linux, x86_64) では何の問題もなかったのだけど、同僚が m1 chip macbook を使っていて、そのマシンからだと docker イメージを使ったビルド処理でエラーが発生する。それは既知の問題で次の issue で報告されている。

このワークアラウンドの1つとして Custom Bundling の仕組みがある。任意の Dockerfile を指定することで任意の Docker イメージやプラットフォーム向けにビルド用の python インタープリターを設定できる。そうしたらビルド処理そのものは通るようになったけど、python distribution (python の依存関係を含めたスクリプト群) が asset として生成されない。この現象自体も cdk でよくある issue として報告されていて cdk.out を削除して再実行したら直ったという報告もいくつかあるものの、同僚のマシンではそれでは解決しなかった。

私が m1 chip macbook をもっていないので cdk のコードを修正して push して同僚に git pull して実行してもらうみたいな作業になっている。このデバッグはなかなか大変。

cdk で既存の eks クラスターを管理すべきか

0時に寝て6時に起きた。

cdk から既存の eks クラスターを制御する

1ヶ月ほど前に検証していた cdk による eks クラスターの helm 管理 を再検証した。kubectlRoleArn にどういった権限をもつ iam role を設定したらよいかがよくわからなくて苦労していた。最終的にそれが理解できて helm 管理もできるようになったのでまとめておく。

kubectlRoleArn - the ARN of an IAM role mapped to the system:masters RBAC role. If the cluster you are importing was created using the AWS CDK, the CloudFormation stack has an output that includes an IAM role that can be used. Otherwise, you can create an IAM role and map it to system:masters manually. The trust policy of this role should include the the arn:aws::iam::${accountId}:root principal in order to allow the execution role of the kubectl resource to assume it.

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_eks-readme.html#using-existing-clusters

aws-auth の configmap に設定されている system:masters に所属している iam role を調べる。

$ kubectl describe configmap -n kube-system aws-auth

この iam role には sts:AssumeRole 権限を与え、trust relationships に arn:aws:iam::${accountId}:root といった root ユーザーを含める必要がある。この root ユーザーの設定がないと次のような権限エラーが発生する。この権限エラーの修正方法がわからなくて苦労していた。結果的には関係なかった kubectlLambdaRole の設定も必要なんじゃないかと検証していたのが前回の作業の中心だった。

An error occurred (AccessDenied) when calling the AssumeRole operation:
  User: arn:aws:sts::${accountId}:assumed-role/xxx is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::${accountId}:role/myrole
Error: Kubernetes cluster unreachable: Get "https://xxx.gr7.ap-northeast-1.eks.amazonaws.com/version

ようやく cdk で既存の eks クラスターをインポートして helm パッケージを管理できるようになった。とはいえ、cdk/cf の実行時間を測ってみると次のようになった。

  • helm パッケージの新規インストール: 約5分
  • helm パッケージのアンインストール: 約25分

これは cdk が helm パッケージを管理するための lambda 環境を構築/削除するときの時間になる。cdk はアプリケーションの stack から nested stack を作成して、そこに lambda や iam role などをまとめて作成する。一度作成してしまえば、バージョンのアップグレードは30秒ほどで完了した。

この振る舞いを検証した上で、cdk で eks クラスターをインポートする管理はやめようとチームに提案した。正しい設定を作ってしまえば運用は楽になると言える一面もあるが、新規に helm パッケージを追加するときのちょっとした typo や設定ミスなどがあると、1回の試行に30分かかる。私がこの検証に1週間以上のデバッグ時間を割いている理由がそれに相当する。お手伝い先の運用ではテスト/本番環境ともにローカルから接続できる状態なので helm コマンドを直接実行した方が遥かに管理コストや保守コストを下げると言える。cdk を使って嬉しいことは helm コマンドでわかるバージョン情報と設定内容が cdk のコードとして管理されているぐらいでしかない。ドキュメントと helm コマンドで管理する方が現状ではよいだろうと私は結論付けた。同じような理由で eks クラスターも cdk ではなく eksctl コマンドで管理されている。

1週間以上の労力と時間を費やしてやらない方がよいとわかったという、一般的には失敗と呼ばれる作業に終わったわけだけど、eks/cdk の勉強にはなった。

設計のリファクタリング

23時に寝て4時に起きた。その後もだらだらしていて変な寝方した。

型定義のリファクタリング

開発が佳境でサービスイン前なのと機能開発は完了しているのでリスクのある作業やリファクタリングなどを主にやっている。別の開発者が作った機能の型定義が曖昧なところをまとめてリファクタリングしていた。若い開発者だから仕方ないことだけど、ジェネリクスとポリモーフィズムを正しく理解できていない。effective java で言うところの抽象骨格実装という設計手法になる。あと ide のリファクタリング機能を使ってコードを書いているのか?public な api の設計がおかしかった。人間が考えてメソッド分割したようには思えない oop らしからぬ手続き的な api になっていた。後から oop としてジェネリックな型定義に落とし込むのがなかなか難しくて丸1日ぐらいやってた。私がドメイン知識をもっていないのもある。以前にもそのコードの pr をレビューしていてよくないコードだとはわかっていたけど、私はドメイン知識がないためにやりたいことがわからないから最低限の品質でマージした後に私がリファクタリングすればいいかと考えていた。若い開発者にプログラミングの設計やコーディングを教えるのは時間を要するのでお手伝いという立場だとなかなか難しい。だから私が直してしまえばいいやというノリで勝手に直した。

openapi generator の設定

3時に寝て6時半に起きた。昨日は夕方に昼寝したので夜は眠れなかった。

openapi generator の x-implements 機能

外部ベンダーの api client の wrapper を実装していて、api client が扱うリクエストやレスポンスを型 (インターフェース) で抽象化できるとよさそうと思って openapi generator の設定を調べていた。maven-plugin の設定と openapi-generator の設定の2つがあるので両方のドキュメントを確認しないといけない。

そんなに都合よくインターフェースを指定できるような仕組みがなければ、最悪は mustache テンプレートをカスタマイズするしかないかなぁとか考えていた。テンプレートを操作すると、今後の保守コストが上がってしまうのでそのメリット・デメリットを比較して考えないといけない。諦めかけていたときに so でこの issue をみつけた。

ちょうどこの5月末にリリースされたばかりの 6.0.0 に x-implements と指定すれば、任意のインターフェースを implements できる機能が追加された。これはスキーマに対する設定なのでテンプレートをカスタマイズするよりずっと保守コストは小さくて済む。

例えば、openapi schema の json で設定すると、コード生成したときにそんな風にインターフェースが付く。

       "SomethingApiResponse": {
+        "x-implements": "com.example.app.MyResponse",
         "title": "SomethingApiResponse",
         "type": "object",
         "properties": {
-public class SomethingApiResponse {
+public class SomethingApiResponse implements com.example.app.MyResponse {

あまりにも意図していた機能をみつけて嬉しくてツィートしてしまった。

法人税の修正申告

1時に寝て7時に起きた。

源泉所得税の納付

ちょうど給料日を過ぎたので所得税徴収高計算書 (納期特例分) の申請を行った。これまでも e-tax で電子申請して振り込みしていたのだけれど、e-tax ソフト (web版) でブラウザから申請していた。今回は windows マシンにインストールされている e-tax ソフトから申請してみることにした。e-tax ソフトは起動時に「追加インストール」という機能があって、申請に必要なモジュールのみをダウンロードしてインストールできるようになっている。20年前で言うところの saas はこうだった。「源泉所得税関係」というモジュールをインストールしないと、所得税徴収高計算書の申請ができない (e-tax ソフトに帳票がインストールされない) 。モジュールを追加インストールすれば、「源泉所得税」という税目から「所得税徴収高計算書 (納期特例分) 」という帳票を選択して、あとは数字を記入して送信するだけ。この申請に電子署名は不要。

法人税の修正申告と欠損金の繰り戻し還付の訂正依頼

国税局の職員さんからの指摘 で提出した書類が誤っていることに気付いた。いくつか訂正箇所を書いておく。

  • 欠損金額とは
    • 正: 税引き後の欠損金額 = 別表1の1の数字をそのまま使えばよい
    • 誤: 税引き前の所得 (税務上は負の所得を欠損金と呼ぶ) を使っていた
  • 法人税と地方法人税の計算は別
    • 欠損金の繰り戻し還付の申請は法人税のみの還付金を算出
    • ↑で求めた還付金に対して(令和元年以降は)10.3%を地方法人税の還付金とする
      • この手続きは不要で別表一に算出した数字を記載すればよい

まずこの申請書の誤りを修正して訂正依頼とする。

さらにこの欠損金の繰り戻し還付の申請の数字を別表1に記載しなければならない。それらが漏れているのと還付申請のためには別表七も提出しないといけない。あと細かい数字の記入漏れの指摘もあった。法人税の確定申告に対して次の5つの書類を修正申告として提出する。

  • 別表一
  • 別表一 次葉
  • 別表四
  • 別表五 (一)
  • 別表七 (一)

税務署の職員さんが訂正する数字を書いてくれていたので、それをみながら e-tax ソフトで数字を修正して紙に印刷してそれを再提出する。おかげで赤字のときの別表書類の書き方もわかった。大半は欠損金の繰り返し還付に関する数字の記入漏れなので今後も赤字の年度があったときにこの内容を踏襲しながら申告すればよい。また1つ行政手続きのノウハウを得ることができた。税務署の職員さんに感謝。

企業サイトの更新

企業サイトを作成してから3年近く経つのでそろそろ初期の頃に書いた内容が現状とあわなくなってきた。あちこち現状とあっていない内容を更新した。本当はデザインを刷新したいと思っている。デザイン刷新のためのチケットを作ったのが2021年7月28日 20:04なのでもうすぐ1年経とうとしている。どんどん時間が過ぎるな。今日のところは、主には 企業情報 の構成を作り直した。それと同時に過去に働いていた会社での業務外発表を除去した。今後は自社の発表のみを掲載する。これには会社としてマーケティング活動をやっていくという意気込みと過去との決別の意味合いもある。トップページに news を5件表示しているところがビルドするタイミングによってそうならないときがあって、実行タイミングによってページングがされたりされなかったりする現象に悩まされている。ワークアラウンドとしては、何回かビルドをやり直せばページングされるときもあるのでそれを待つみたいな、どうしようもないやり方でこの場は凌いでいる。

k8s のアップグレードをやってみた

0時に寝て6時半に起きた。起きてから1時間ほどだらだらしてた。

ストレッチ

今日の開脚幅は開始前161cmで、ストレッチ後163cmだった。先週と同じなので現状維持とも言えるし、よい状態を維持しているとも言えるかもしれない。もう1年以上通っているせいか、なにかポイントが溜まっていて使わないといけないという話しで今日は20分延長でやってくれた。とは言っても、基本的なストレッチ項目が変わるわけではなく、いつもより伸ばす時間や手順が少し増えているぐらいだった気がする。今週はとくに腰の負荷もあまり感じなかったせいか、いつもの右腰の張りもなかったように思う。トレーナーさんに聞くと、暑くなると筋肉は伸びやすくなるので季節要因でストレッチをしたときの伸び具合が変わるのは普通とのこと。調子がよくなってきたのでこのまま好調を維持したい。

eks (k8s) のアップグレード

お手伝い先のお仕事がもうすぐサービスインなのでそれまでにリスクのある作業をやっとこうみたいな状況にある。たまたま eks (k8s) のバージョンを 1.21 から 1.22 にあげようと思い立って、木曜日に提案したら、どんな障害が起きるかわからないので他メンバーがテスト環境を使っていない時間帯で作業した方がよいだろうという話になって土日にやることにした。

何が起きるか分からなくても、土曜日から始めて致命的なトラブルに見舞われても1日もあれば解決できるだろうという見通しで作業を始めた。その見通しも「私がやれば」という前提に成り立っている。良くも悪くも私がやろうと言ったことに反対されることはほとんどないが、それは私が言ったことは一定時間に私がすべてやり切るという信頼に基づいている。本当の意味でできるかどうか分からないことを必要以上に抱え込んでしまうときもあるのでバランス感覚は必要かもしれない。言わばサービス休日出勤だし、なぜ私がやっているかと言うと、システムの運用や保守の展望を考えたら、サービスインの前にインフラのバージョンを上げておく方が将来の保守コストを下げることに繋がるという1点のみに重要性を見い出していて、それをもっとも強く主張しているのが私だからという理由。

結論から言って2時間でアップグレード作業を完了した。1つ手順漏れがあって、アプリケーションの pod がすべてエラーになるというトラブルに見舞われたものの、すぐ手順漏れに気付いて難なく復旧できた。今日はテスト環境のアップグレードをしたわけだけど、また後日、本番向けの作業手順書を作れば、ほぼタウンタイムなしで1時間もあればアップグレード作業を完了できそうな見通しではある。

実際はミスもあったので次の順番でやったわけではないが、おそらくこの手順でやれば正しいはず。

  1. aws cli と eksctl コマンドのインストール
  2. aws のアップグレードドキュメンを読む
  3. cert-manager のアップグレード (1.1.1 から 1.5.4)
  4. aws-load-balancer-controller のアップグレード (2.2.0 から 2.4.2)
  5. k8s control plane のアップグレード (1.21 から 1.22)
  6. (オプション: 不要) autoscaler のアップグレード
  7. (オプション: 不要) gpu サポートノードのアップグレード
  8. vpc cni プラグインのアップグレード (1.7.5 から 1.11.2)
  9. coredns プラグインのアップグレード (1.8.4 から 1.8.7)
  10. kube-proxy のアップグレード (1.21.2 から 1.22.6)
  11. k8s nodegroup のアップグレード (1.21 から 1.22)
    • k8s ノードが存在する nodegroup をアップグレードするとそのインスタンスが再作成されて pod が再デプロイされる

細かい手順は aws のドキュメントの指示に従いながらやったらできた。add-on と self-managed add-on の種別の違いがあったり、helm と k8s manifest の手順が別々だったり、どのバージョンからのアップグレードかで作業手順が異なったりと、ドキュメントをちゃんと読まないと正しい作業手順がわからない。基本的にはドキュメント通りの作業で完了できた。

もくもく会

アップグレード作業を終えてから1時間ほど残っていたので16時から 【三宮.dev & KELab 共催】もくもく会 に参加した。今回は Kobe Engineers Lab さんと共催ということで 120 WORKPLACE KOBE で開催された。Kobe Engineers Lab の主催者の会社が 120 workplace でオフィスを借りているため、会議室を5時間/月まで無料で借りられるという。私も過去に何度か 120 workplace のコワーキングスペースで作業したこともあった。久しぶりに行ってよい場所だとは思う。会議室は初めて入ったけど、10人ぐらいは余裕で作業できる大きなテーブルがあって広くてよかった。終わってからわたなべさんと3時間ほど立ち呑みしてた。

はんなりビジネス

21時から はんなりビジネス #0 に参加した。おがわさんがまた新しいことやるんだなと思って興味本位で参加してみた。現実の課題に対してコミュニティの有志を募ってチームで取り組んでみたら、問題解決能力も身についてプログラミングの知識を活かしてより実践的なスキルが身に着いてよいのではないかといったところから始まった企画らしい。今日は初回だったので参加者でどういう取り組みがよいのかを雑談してた。まだまだこの先どうなるかわからないけど、私はあまりこの手の取り組みには懐疑的かなぁ。自分たちにとってちょうどよい課題レベルの対象をみつけるのは難しいし、誰でも参加できるオープンなビジネスコンテストやアイディアソンが本当に大事な問題を扱っているかも怪しい。現実の課題はお仕事でいくらでもあるので、それをコミュニティでやろうと思うとニッチな何かになるか、価値があるかどうかよりも本人がやりたいかどうかの目的になってしまうような気もする。とはいえ、私自身、ビジネス力はまったくないのでなにかしらやっているうちに価値に気付くこともあるかもしれない。もうしばらく様子をみてみる。

余白談義

0時に寝て6時に起きた。だいぶ復調してきて朝起きれるようになってきた。

歯科検診

3ヶ月ごとの定期検診。前回 は2年ぶりにレントゲンをとったので新しい歯のレントゲン写真をみせていただいた。とくに変わりなく、親知らずにゴミが溜まるスポットがあるから、親知らずを抜いた方がいいのはいいけど、下側の親知らずを抜くのは大変だから痛くなってからでもよいかも?みたいな話しをした。いま3ヶ月ごとに定期検診して親知らずのゴミスポットの掃除をしてもらっているからそれでもしかしたら大丈夫なのかもしれない。あと1箇所だけ銀の詰めものと歯が精密に一致していないところがあって、その隙間にゴミが溜まりやすいとのこと。急がなくてもよいけど、いずれ付け直した方がよいらしい。

隔週の雑談

顧問のはらさんと隔週の打ち合わせ。今日の議題は次の3つ。

  • お手伝い先の契約更新についての話題
  • カフーツさんとワーケーションの話題
  • jjug ccc 2022 spring ふりかえり

先日の記事 でも「余白」という概念が個人的にはまったのであちこちで余白談義をしている。デザインで言うところの余白とは何かをはらさんに聞いてみたところ、次のようなものだという。

  • 空白というコンテンツである
  • 他に使ってはいけない領域である

開発における余裕とかゆとりのことを余白と言ってしまうと、デザインの文脈とは一致しないという話しになった。ワーケーションの文脈では予定調和じゃない時間を指すといとうさんは仰っていた。何かをやるための計画を立てた時間ではない。何もしなくてもよいし、思いつきで何かをしてもよい。先日の記事で、課題管理の文脈でメタ課題の抽出や他人のチケットにコメントしたりするのも余白の1つではないかと私が書いたのはその行動が必須というわけではない。誰かから指示されてやっているわけでもないという行動が、ワーケーションにおける予定調和じゃない行動と共通点があるのではないかと考えたから。

私が開発における余白を余裕やゆとりのように解釈して発言したので余っているものというニュアンスになってしまったけど、その時間は余っているものではなく、明示的に明けておくべき時間のように捉えたらデザインの文脈で言う余白にも近い概念になるかもしれない。開発だと想定外の追加作業や要件漏れなどのために確保しておく時間をバッファと呼んだりもする。例えば、開発の作業時間を1日8時間、1週間(5日間)で40時間と見積もるから余白がなくなってしまうであって、仮に1週間の余白を3時間と先に確保してしまって、37時間を開発の作業時間としてしまえば余白がなくなるということない。その余白時間に業務に関係ない勉強会をやってもいいし、自分の調べたいことに費やしてもいいかもしれない。私が金曜日を非稼働日として業務委託の作業をやらずに雑談していることにも通じる。

その後、余白談義からスクラムの課題や展望の話しなどにも発散して盛り上がった。スクラムには余白がないから。まだまだ私自身、余白の言語化に曖昧なところがあるので今後も意識しながら概念や価値を言語化していくように努めたい。

assertj を使ってみた

0時に寝て6時に起きた。

assertDeepEquals を作った

AssertJ というアサーションライブラリを使って assertDeepEquals を実装した。

junit4 では hamcrest という matcher が使われていて、それが assertDeepEquals 相当の機能を提供していたが、それが junit5 では提供されなくなったので自分で実装するか、アサーションライブラリを別途使う必要がある。

現時点でJUnit5ではHamcrestのMatcherは提供せず、使用者が自由に選択する方針で進んでいます。そうなった場合、標準でサポートされるassertTrueやassertEquelsなどだけでは、ちょっと頼りなく車輪の再発明になりそうなので、候補になりそうなHamcrestとAssertJのよく使いそうなメソッド比較表を作りました。

JUnitのアサーションライブラリHamcrest,AssertJ比較

2.4.2. Third-party Assertion Libraries によると、junit は基本的なアサーション機能を提供し、より強力なアサーションはサードパーティ製の好きなライブラリを使ってくれみたいなことが書いてある。軽く github でソースコード検索しても、みんな自前で作っているんやなということも分かる。

hamcrest はもう保守されていないようにみえるので assertj を使うことにした。assertj の機能を使うと assertDeepEquals を次のように実装できる。直接 assertj を使ってもよいのだけど、assertXxx という名前で使えた方が junit ベースのテストのアサートの統合性があるし、いまお手伝い先では myapp-test のような、テスト向けの共通ライブラリを提供していて、すべてのプロジェクトで既に使っているので assertj の依存関係を追加しなくてもすぐに使えるというぐらいの利便性を提供するだけのユーティリティになる。

public class Assertions {

    public static final void assertDeepEquals(Object expected, Object actual) {
        assertThat(expected).usingRecursiveComparison().isEqualTo(actual);
    }

    public static final void assertDeepEquals(Object expected, Object actual, String... fields) {
        assertThat(expected).usingRecursiveComparison().comparingOnlyFields(fields).isEqualTo(actual);
    }

    public static final void assertDeepEqualsIgnoringFields(Object expected, Object actual, String... fields) {
        assertThat(expected).usingRecursiveComparison().ignoringFields(fields).isEqualTo(actual);
    }
}

k8s の cronjob を検証中

0時に寝て6時に起きた。寝不足を解消して体調が戻ってきた。

k8s の cronjob

バッチ処理を Kubernetes: CronJob で作る。一通り設定して minikube で検証して eks 上でも動くようになった。

apiVersion: batch/v1
kind: CronJob
metadata:
  name: my-app-hourly-job
spec:
  schedule: "5 */1 * * *"
  concurrencyPolicy: Forbid
  startingDeadlineSeconds: 600
  jobTemplate:
    spec:
      backoffLimit: 0
      template:
        metadata:
          labels:
            app: my-app-hourly
          annotations:
            dapr.io/enabled: "true"
            dapr.io/app-id: "my-app-hourly"
        spec:
          containers:
          - name: my-app-hourly-job
            image: my-app-image
            imagePullPolicy: Always
            env:
            - name: BATCH_ENV
              value: "dev"
            command:
            - "/bin/sh"
            - "/app/scripts/my-app.sh"
            - "param1"
            - "param2"
          restartPolicy: Never

command の設定がわかりにくい。さらに k8s のドキュメントのサンプル設定も誤解を招くような例になっている。どうも実行できるのは1つの cli だけで、複数コマンドを指定できるわけではない。シェルスクリプトを docker イメージに含めて、そこで任意のスクリプトを実装した方がよいだろう。

  • “/bin/sh”
  • “/app/scripts/my-app.sh”
  • “param1”
  • “param2”

この設定は次の cli として実行される。

/bin/sh /app/scripts/my-app.sh param1 param2

How to ensure kubernetes cronjob does not restart on failure によると、バッチ処理が失敗したときに再実行したくないときは次の3つの設定をする。

  • concurrencyPolicy: Forbid
  • backoffLimit: 0
  • restartPolicy: Never

restartPolicy が Never 以外だと、エラーが発生すると永遠に再実行されてしまうので障害時に2次被害を増やしてしまう懸念があったような気がする。

あと、うちの環境は dapr 経由で他の pod サービスと通信しているので dapr を有効にしないと pod 間通信ができない。dapr はデーモンでずっと起動しているからバッチ処理の終了時に daprd も shutdown してやらないといけない。Running Dapr with a Kubernetes Job にその方法が書いてある。daprd を shutdown しないと、pod のステータスが NotReady のままで Completed にならない。

まだまだよくわかってないので Jobs のドキュメントに一通り目を通そうと思っている。

maven で executable jar を作る

4時に寝て7時に起きた。

maven での executable jar の作り方

gradle では作ったことがあったけど、maven では初めてなので要領がわかっていない。

これらの記事を読むと、maven-assembly-plugin を使えばいいのかな?とまずはこのプラグインで検証を始めた。古くからあるプラグインなので実績は十分なのだけど、もうあまり保守されていないのか、他プラグインから jar のマニフェストに書き込んで git のリビジョン番号が連携できてなかったり、通常の jar の生成処理を置き換えられなかったりと、あまり使い勝手のよいものではなかった。あと log4j2 と相性が悪くて意図したように設定ファイルを読み込んで初期化ができない。

main ERROR Error processing element EcsLayout: CLASS_NOT_FOUND
main ERROR Unable to locate plugin type for EcsLayout
main ERROR Unable to locate plugin for EcsLayout
main ERROR Could not create plugin of type class org.apache.logging.log4j.core.appender.ConsoleAppender for element Console:
  java.lang.NullPointerException: Cannot invoke "org.apache.logging.log4j.core.config.plugins.util.PluginType.getElementName()"
  because "childType" is null java.lang.NullPointerException:
    Cannot invoke "org.apache.logging.log4j.core.config.plugins.util.PluginType.getElementName()" because "childType" is null

この厄介な問題をデバッグするよりも、すでにうまくいくことがわかっている spring-boot-maven-plugin を使った方が簡単そうだったのでそうすることにした。不要な spring boot 関連の jar なども executable jar や docker イメージに含まれてしまうことだけがデメリット。そこだけ目を瞑れば log4j2 の初期化エラーも起きず、正常に動作した。やっぱり最近のアプリケーションで使われているプラグインはちゃんとしてるねみたいな話しにしておく。次の設定だけでうまくいった。

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <mainClass>com.example.myapp.Main</mainClass>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>repackage</goal>
            </goals>
        </execution>
    </executions>
</plugin>

log4j2 の設定ファイルの動的な読み込み

0時に寝て7時に起きた。

バッチ処理モジュール

cli でバッチ処理モジュールを作った。コマンドラインの引数パーサーと yml のパーサーを使うことにした。

ロガー実装に log4j2 を使っているので設定ファイルはアプリケーションの設定ファイルと log4j2 の設定ファイルの2つになる。それぞれ環境ごとに用意してエントリーポイントから起動したタイミングで明示的に設定ファイルを読み込むようにした。

log4j2 の yml 設定ファイルを動的にどうやって設定するかはドキュメントにもとくに書いてなかった気がする。log4j2 のソースコードやテストコードを読みながら次のようにしたら反映された。

public static Config load(BatchEnvironment env) {
    var path = String.format("config-%s.yml", env.getName());
    var inputStream = ConfigUtil.class.getClassLoader().getResourceAsStream(path);
    var yaml = new Yaml(new Constructor(Config.class));
    return yaml.load(inputStream);
}

アプリケーションの設定は yml 設定に対応する Config クラスを定義しておいて次のようにして読み込む。

public static void initializeLogSettings(BatchEnvironment env) throws IOException {
    var path = String.format("log4j2-%s.yml", env.getName());
    var inputStream = ConfigUtil.class.getClassLoader().getResourceAsStream(path);
    var source = new ConfigurationSource(inputStream);
    var configuration = new YamlConfigurationFactory().getConfiguration(null, source);
    Configurator.initialize(configuration);
}

ちょっとした cli を作るときにちょっとしたライブラリがあると楽でよい。

欠損金の繰り戻し還付の申請の誤り

国税局から電話がかかってきた。初めて提出した欠損金の繰り戻し還付の申請があちこち間違ってますよと。申請書類と一緒に法人税の申告書もみてもらっていて、還付申請した金額も申告の別表1に記入する必要があって、それも一緒に修正してねという話し。法人税の修正申告と還付の訂正依頼の2つが必要とのこと。税務署の人たちは本当に丁寧で親切にあれが間違っている、これが間違っていると教えてくれる。素人が法人決算やっているので初めて行う手続きの間違いはつきものだけど、税務署の人たちが教えてくれるので本当に助かる。感謝。