Posts for: #Kubernetes

nodeSelector を試す

4時に寝て8時に起きた。久しぶりに寝坊した。

k8s の nodeSelector

先日 定期/バッチ処理を k8s の cronjob にすべて移行 した。すでに本番運用もしていて調子もよさそうにみえる。あと残課題としてバッチ処理とアプリケーションサーバーの pod がデプロイされる k8s ノードを分割したい。現時点では、バッチ処理の負荷は小さいから同じスペックのインスタンスの k8s ノード上で混在させて運用している。しかし、いずれ運用上の問題になる懸念がある。そのため、バッチ処理のみを実行する k8s ノードを管理したい。次のドキュメントに書いてある。

k8s のドキュメントによると、大きく分けて nodeSelector と Affinity という2つのやり方がある。前者はラベルでフィルターするシンプルな仕組み、後者はさらに複雑な要件に対応するもの。いまのところ、ただ分割できればよいのでシンプルな nodeSelector で実装してみることにした。

  • nodeSelector
  • Affinity and anti-affinity

余談だが、nodeSelector はいずれ Affinity に置き換わるので deprecated だと一時期ドキュメントに書かれていたらしい。具体的に決まっていることでもないため、nodeSelector: when will it be deprecated? #82184 によると deprecated という文言を含む文章がその後に削除された。Affinity は高機能且つ高コストであることから、(現時点では) nodeSelector はシンプルで推奨すべき方法とまで書いてあるのですぐになくなるわけではなさそう。

minikube で nodeSelector の検証を始めたんだけど、いくつかうまくいかないことがあって断念した。multi-node 機能を使って controll plane と worker ノードの2つを起動できたけど、worker ノードから docker host にアクセスできなかった。何かしら設定が必要なのか、別途レジストリが必要になるのかよくわからなかった。あと node にラベル設定したときに worker ノードにラベル設定しても minikube を再起動するとそのラベルが消えてしまっていて保持されないようだった。ちょっと調べてローカルの環境を作るのが面倒になったので早々に断念した。

lambda から cronjob へ

0時に6時に起きた。今日は晴れたので自転車通勤。

優雅にドキュメントを書きながら障害対応

昨日は凸凹しながら乗り切ってサービスイン2日目。今日から外部システムとの連携なども絡んでくる。昨日の今日なんで何か起こるだろうと思いつつ、暇だったらドキュメント書くタスクがいくつも溜まっているのでそれを片付けるかと業務を始めた。私ぐらいの人間になると、いつ凸凹が発生してもよいように、この日のために取っておいたようなドキュメントタスクがいくつも溜まっている。午前中に私の出番はなく、優雅にドキュメントの1つを完成させた。

以前から定期実行やバッチ処理は lambda 関数をトリガーに作られていた。それらを serverless framework から cdk へ移行した んだけど、その後にバッチ処理を k8s の cronjob で実装した 。この cronjob が思いの外、うまくいって、私がフルスクラッチで cli を作っているのだから、私からみてさいきょうのばっちしょりの土台を実装している。定期実行もすべて cronjob でやればいいやんと気付いて、過去に lambda 関数 (cdk/python) で実装したものをすべて移行することに決めた。昨日の流れからわかるように過去に作成済みの一連の lambda 関数はまだ本番環境にデプロイされていない。m1 chip macbook 問題 で同僚のマシンからデプロイできないという不運もあったんだけど、もうデプロイしなくていいよ、すべて cronjob で置き換えるからと伝えて2つ移行した。あと半日もあれば完了できそうな見通し。

lambda 関数から cronjob への移行作業をしていると、本番環境でのバッチ処理の一部のロジックが誤っているとわかってそれを修正したり、当然のようにテスト環境のデータも誤っているので正しいかどうかは本番環境にデプロイするしかないみたいな凸凹した状況を横切りながら本日の作業を終えた。いくつか残課題は残っているものの、明日中には平常業務に戻れるぐらいの状況にはなりつつあるのかもしれない。サービスイン2日目を終えて致命的な問題は起こっていないようにみえる。ひとまずはよかったという感じ。

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 の勉強にはなった。

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

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 のドキュメントに一通り目を通そうと思っている。

法人として消費税を納めた

5時に寝て7時過ぎに起きた。前日の夜から法人決算の電子申告に取り組み始めた。本当は紙でやるつもりだったんだけど、eltax が快適だったので e-tax も衝動的にやってみたくなった。

消費税と地方消費税の申告

法人決算 の一部。今回が初めての消費税と地方消費税の申告になる。簡易課税で支払う。

消費税は、国税(国に納付する税金)であり消費税の納税義務がある事業者が納付します。地方消費税とは、 消費税と同様で商品の販売やサービスの提供などの取引にかかる税金 です。消費税との違いは、 地方消費税は国税ではなく地方税(都道府県や市町村に納付する税金)という点です。 しかし実際に納付するときは消費税と分けて納付はせずに、 消費税と一緒に地方消費税を所管税務署へ納付します。

消費税と地方消費税の違いは?納付対象者や納付方法、計算の仕方まで徹底解説!

freee で出力した書類をみながら e-tax の画面で同じ書類の項目を埋めていくだけの作業。1つだけバリデーションエラーが発生して、何度やり直しても数値は正しいようにみえるので無視して処理を継続することにした。メッセージにも値が正しければ継続してくださいと書いてあるのでバリデーションがバグっているのだろうと推測する。書類を作成して、署名して、送信して、納付情報が返ってきて、pay-easy で納付額を振り込む。1時間ほどで完了できた。

eks (k8s) から alb の管理

eks (k8s) に aws-load-balancer-controller をインストールすると k8s 上のリソースとして alb を管理できるようになる。

具体的には k8s の Ingress と Nodepoint リソースから次の3つのリソースを生成してくれる。

  • application load balancer
  • http listener
  • target groups

alb からのヘルスチェックは次のようにエンドポイントを記述する。spring boot だと Actuator という web api がヘルスチェックの機能を提供している。

alb.ingress.kubernetes.io/healthcheck-path: /actuator/health

alb.ingress.kubernetes.io/scheme の設定で alb を配置するサブネットを指定できる。デフォルトは internal になる。

private subnet に配置するとき

alb.ingress.kubernetes.io/scheme: internal

public subnet に配置するとき

alb.ingress.kubernetes.io/scheme: internet-facing

helm を調べた

1時に寝て6時半に起きて8時に起きた。前日は資料づくりで遅くまでオフィスに残っていたせいか、なんか寝坊した。

helm 調査

k8s 上の datadog-agenthelm で管理されていて、あるバージョンから dapr も helm 管理できる ようになった。dapr は cli からもインストールできるけど、helm のことをよくわかってなかったので調べることにした。そんなたくさん記事をみたわけではないけど、いくつか記事を読んで quora のやり取りが一番よかった。

ざっくりまとめるとこうかな。

  • helm は oss 且つ cncf の公式プロジェクトだからまぁ安心
  • helm はサードパーティのパッケージのインストールや設定の利便性を高める
    • k8s はテンプレート機能が弱いので共通設定と特定環境向けの設定を管理するのがあまり得意ではない
    • セキュリティを考慮した k8s 設定は自分でやるよりコミュニティに任せた方がよい場合もある
    • パッケージなのでバージョン管理は得意
  • helm は k8s 向けのパッケージマネージャとレポジトリマネージャーとマーケットプレイスを組み合わせたみたいなもの

k8s 上でサードパーティのパッケージを自分で設定したい特別な理由がない限りは helm を使うのがよさそうという結論になった。

spring boot の環境とログ設定

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

spring のプロファイル設定

spring の Profiles の仕組みを使って環境ごとの設定を作る。デプロイは k8s で管理しているため、spring boot の Externalized Configuration の仕組みを使って、環境変数から application.yml に定義された設定を書き換える。k8s は kustomize で管理していて prod, test, dev の3つの環境で任意の設定を記述できる。

問題はログ出力の設定を環境ごとに変えたい。具体的には datadog に連携されるログは構造化ログ (json lines) を、ローカルの開発ではコンソールログをみたい。Log4j Spring Boot Support によると、1つの設定ファイルに複数のプロファイル設定を記述できるようにもみえるけど、実際にやってみたらうまく動かなかった。xml ではなく yml を使っているせいかもしれないし、私の記述方法が誤っているのかもしれない。いずれにしても yml で複数のプロファイルを設定しているサンプルをみつけられなかった。

そこで Different Log4j2 Configurations per Spring Profile をみて、環境ごとにログ設定ファイルも分割することにした。application.yml には次のように記述する。

spring:
  profiles:
    active: dev

logging:
  config: classpath:log4j2-${spring.profiles.active}.yml

ローカル開発向けの lgo4j2-dev.yml は次のようになる。

Configuration:
  status: warn
  name: YAMLConfig
  appenders:
    Console:
      name: STDOUT
      target: SYSTEM_OUT
      PatternLayout:
        Pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS}[%t]%-5level %logger{36} - %msg%n"

k8s のマニフェストで環境変数を次のように定義すれば prod というプロファイルが設定される。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-service
spec:
  template:
    spec:
      containers:
      - name: my-service
        env:
        - name: spring.profiles.active
          value: "prod"

クラウド環境向けの log4j2-prod.yml は次のようになる。

Configuration:
  status: warn
  name: YAMLConfig
  appenders:
    Console:
      name: STDOUT
      target: SYSTEM_OUT
      EcsLayout:
        serviceName: my-service
        serviceNodeName: null
        includeMarkers: true
        KeyValuePair:
        - key: type
          value: app

k8s のロールバック

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

k8s のロールバック

Rolling Back to a Previous Revision をみながらすぐできた。ロールバックもこれまでと同様、github actions の workflow dispatch で管理できるようにした。基本的にはこれだけでロールバックできる。

$ kubectl rollout undo deployment/my-app-deploy

ちょっと工夫したこととして、デプロイ時に kubernetes.io/change-cause というアノテーションに git のリビジョンもセットしておくと確認するときにちょっと楽ができる。apply した後の deployment リソースに docker イメージのタグ情報 (= git のリビジョン) を書き込んでおく。

$ kubectl apply -k ${{ env.DEPLOYMENT_ENV }}
$ kubectl annotate deployment my-app-deploy kubernetes.io/change-cause=${{ env.IMAGE_TAG }} --overwrite=true

kubectl から履歴をみたときに k8s のリビジョンがどの git のリビジョンを使っているかがわかりやすい。デフォルトでは何も設定されていないかもしれない。

$ kubectl rollout history deployment/my-app-deploy
deployment.apps/my-app-deploy
REVISION  CHANGE-CAUSE
15       <none>
16       <none>
17       <none>
18       <none>
19       <none>
20       <none>
21       <none>
22       <none>
24       1f17a22a6659ea0714a21fca034645cd191e189b
27       a84e113d8b7c124178b58e2f40f57b00aae65485 
28       dcf3552db0668d416ed880f6e896455d7bab194c

kustomize の動的な設定

23時に寝て2時に起きてそこから断続的に寝たり起きたりを繰り返して6時に半に起きた。ウクライナ情勢のニュースや記事ばかり読んでてあてられた。

kustomize の動的な設定

kustomize で管理している image のタグをデプロイ処理の中で動的に変更したい。パラメーター渡しなり環境変数なり、なにかしらあるだろうとは予測している。Demo: change image names and tags のサンプルによると、次のように実行すればよいみたい。

$ kustomize edit set image busybox=alpine:3.6

次のような kustomization.yaml をセットしてくれるみたい。

images:
- name: busybox
  newName: alpine
  newTag: 3.6

タグのところをリビジョン指定できればいいだけなのでとくに SECRET を使う必要もない。このやり方で書き換えた newTag が POD のデプロイ対象になってくれればよいはず。

wiki のドキュメント整理

23時に寝て4時半に起きた。昨日の帰りに自転車でこけて胸を強打してひたすら痛い。起き上がるのも痛い。安静にしてた。

kubernetes のログ管理と datadog-agent のログ連携不具合

先日、datadog にログ連携されていない不具合 が発生していて、その1次調査を終えたことについて書いた。緊急対応としては datadog-agent を再起動することで改善することはわかっていたので、その後、kubernetes のログ管理と datadog-agent がどうやって kubernetes クラスター上で実行されているアプリケーションのログを収集しているかを調査していた。今日は wiki に調査してわかったことなどをまとめていた。

kubernetes クラスターはコンテナランタイムに docker を使っていて、アプリケーションの stdout/stderr を docker の logging driver にリダイレクトし、JSON Lines に設定された logging driver が kubernetes ノード上にログファイルとして出力する。datadog-agent は autodiscovery 機能で pod の情報を常にポーリングしていて、pod が新たにデプロイされたらログファイルを pod 内にマウントして、そのマウントしたログファイルを読み込んでログ収集していると思われる。datadog-agent から pod の情報を取得するには kubernetes のサービスアカウントを使っていて、その credential が projected volume としてマウントされて pod 内から利用できる。その credential を使って kubelet api にリクエストすることで pod の情報を取得している。

文章で書けばたったこれだけのことなんだけど、たったこれだけのことを理解するのに次のドキュメントを読んだ。実際の調査のときはわからなかったのでもっと多くのドキュメントを読んでいる。いま書いたことを理解するならこのドキュメントを読めば理解できるはず。

ドキュメントに書いてあることを深く理解するために、kubernetes と datadog-agent のソースコードも読んだ。どちらも go 言語で実装されている。

kubectl logs の振る舞いを確認するだけでも、ソースコードからは実際のログファイルを open してストリームを返しているところはわからなかった。api 呼び出しが連携されて抽象化されていて、コンポーネントの役割分担があって、何も知らずにコードを読んでいてもわからなかった。Kubernetes の低レイヤーのところは Container Runtime Interface (CRI) という標準化を行い、1.20 から docker は非推奨となり、将来的に CRI を提供する実装に置き換わるらしい。ログファイルを open する役割は CRI の実装が担うんじゃないかと思うけど、そこまでは調べきれなかった。また機会があれば CRI の実装も読んでみる。