水底

ScalaとかC#とかk8sとか

Docker で動かすプロセスをちゃんと考える会

docker をなんとなく使っていて「Ctrl-C で抜けようとしてもコンテナが止まらない」・「docker stop すると 10 sec くらいかかる」・「k8s で rolling upgrade になんか時間がかかる」といった経験はないでしょうか。もしくは以下のようなことをしていないでしょうか?

  • とりあえず動くからという理由で npm run start のような形で docker の実行プロセスを指定している
  • 何か良からぬことが起こるらしいので tini を使っている
  • もともと単一プロセスだけ実行される docker image だったが runtime に前処理を差し込みたくなり CMD ["sh", "-c", "sh setup.sh && node server.js"] のような書き方をしている

バッチリ設定しているから大丈夫だぜ!という方はブラウザバックで大丈夫です。逆に心当たりがある方は結論だけでも見ていくとハッピーになれるかもしれません。 以下ではサーバー的なプロセスを前提とします。特に nodejs 環境の話をしますが、それ以外のアプリケーションでも根本は共通です。 graceful shutdown の具体的な実装方法は利用しているフレームワークによるところが大きいので今回は触れません。

結論

常に意識するべきこと

  • CMDENTRYPOINT も常に exec 形式にする
  • 前処理が必要なときは、
    • 処理が簡単なとき: CMDsh で wrap しつつ、最後のメインの処理は exec で呼び出す
      • e.g., CMD ["sh", "-c", "sh setup.sh && exec node server.js"]
    • 処理が複雑なとき: ENTRYPOINTentrypoint.sh を配置し、最後に exec "$@" する。CMD にメインの処理を置く。 e.g.,
# entrypoint.sh
# some complex preprocessing
# ./setup-something-1.sh
# ./setup-something-2.sh
# ...
# finally
exec "$@"

# ------------------

# Dockerfile
ENTRYPOINT ["sh", "entrypoint.sh"]
CMD ["node", "server.js"]

nodejs 関連で意識すべきこと (nodejs 以外でも取るべき方針は同じ)

  • graceful shutdown するための SIGTERM (& SIGINT) handler を常に実装する
  • CMD では node を使う
    • npm, yarn などは挟まない
    • tinidocker run --init も基本的には不要
      • docker run --init に依存すると、local の docker では利用できるがいざ k8s で動かそうとすると手を加える必要が出てきてしまう
      • どうしてもゾンビプロセスを生む可能性が避けられないプログラムを動かしたいときに限り、tini を挟んだ方が良い
        • k8s で動かす想定でも同じ。shareProcessNamespace: true も解決策になりうるが、そもそも docker image に閉じた問題であるためおすすめしない。

その他のケースの話

  • CMD で変数展開など shell 固有機能が実行コマンドに必要なときは entrypoint.sh を作り、その中で最終的に exec <command> になるようにする
  • PID=1 だとおかしくなるようなプロセスやゾンビプロセスを生む可能性が避けられないプログラムを動かしたい場合に限り ENTRYPOINT として tini を挟む必要がある
    • PID=1 で動かしたときに SIGTERM などで死んでくれてそれで良いなら tini を挟んでも何も変わらない
    • node は signal handler を適切に実装すれば PID=1 でも期待通り動くようになるので不要

おまけ

結論に至るまで

この結論に至るためには結構いろんなことが必要だったので、ごく初歩的なところから雑に並べておきます。

linux process の振り返り (1)

子プロセスを作成すると親プロセスとは異なる PID を持つことになります。 例えば sh -c 'echo hello' を実行したら、sh が親プロセスとなり、echo が子プロセスとして実行され親から wait されます。

linux process の振り返り (2)

exec コマンドで実行された場合は子プロセスを作ることなくそのプロセスで実行されます。 例えば sh -c 'cmd1 && exec cmd2' を実行したら、cmd1sh の子プロセスとして実行されますが cmd2sh と同じプロセスで動くことになります。

linux process の振り返り (3)

親プロセスが先に死んでしまった場合、子プロセスは孤児プロセスになります。孤児プロセスは kernel によって reparenting され PID=1 の子になります。一般的な linux サーバーのように PID=1 で init が動いている場合、元の親プロセス代わって reparenting されてきたプロセスを wait します。

Docker で実行されるプロセス

単純に ENTRYPOINTCMD (k8s 環境であれば加えて command, args) を組み合わせて PID=1 のプロセスとして実行されます。

docker stop

docker stop を実行すると、対象のコンテナの PID=1 プロセスに SIGTERM が送られます。そのプロセスが SIGTERM により速やかに終了すれば docker stop もそのまま終了します。 ただし SIGTERM がタイムアウト (デフォルトだと 10 sec) 経ってもプロセスが終了しない場合、SIGKILL が送られます。docker stop でちょうど 10 sec 程度かかる場合は SIGTERM でプロセス終了ができていないことを疑うとよいでしょう。

docker entrypoint での tini や docker run --init

プロセス管理

これらを使うとコンテナ内の PID=1 で実行されるプロセスが特別なものになり、ユーザーが実行したい node のようなプロセスはその子プロセスとして実行されます。 PID=1 で動くプロセスは linuxinit と同じように子プロセスの wait をしてくれます。これは孤児プロセスが発生しても PID=1 への reparenting 後に wait されるため、ゾンビ化を防げることを意味します。例えば nodesh のような一般的なプロセスはこのような機能を持ち合わせていないため、それらが PID=1 で動作しているときに孤児プロセスが発生するとやがてゾンビプロセスになってしまいます。

signal handling

PID=1 に来た signal は子プロセスに伝搬されます。signal を伝搬するだけなので、当然 tini や docker run --init を利用しただけでは graceful に終了 (例えば Web サーバーであれば処理中のリクエストを待ってから exit) されません。依然として graceful shutdown のためには適切な signal handler の実装が必要です。

nodejs の挙動

nodejs は PID=1 で実行することが想定されていないため、PID=1 で実行したときとそれ以外で挙動が異なります。 docker-node/docs/BestPractices.md at main · nodejs/docker-node · GitHub

PID=1 以外で signal handler が実装されているとき

signal handler が実装されている場合はそれらを実行します。handler で process.exit() などをした場合に限りプロセスが終了します。

PID=1 以外で signal handler が実装されていないとき

デフォルトだと SIGTERM、SIGINT などの signal を受け取ると雑に exit します。

PID=1 で signal handler が実装されているとき

signal handler が実装されている場合はそれらを実行します。handler で process.exit() などをした場合に限りプロセスが終了します。

PID=1 で signal handler が実装されていないとき

デフォルトだと SIGTERM、SIGINT などの signal を無視します。つまり docker stop してもプロセスは平然と生き続けます。(nodejs v10.9.2 で確認) これが巷で「docker で node を動かすときは tini のようなものが必要だ」と言われる理由だと思います。ただし一つ前の項目で述べたように、signal handler を適切に実装すれば SIGTERM で graceful にプロセスを終了できるため、そちらを優先すべきだと思います。

npm の挙動

npm run は内部的に @npmcli/promise-spawn を利用して子プロセス shスクリプトを実行します。 npm は SIGTERM、SIGINT などの signal を受け取ると基本的にエラーを吐いて終了します。ただしこのとき子プロセスに signal が送られるかは環境 (@npmcli/promise-spawn の実装) に依存します。余計な混乱を避けるため、この辺りに依存したシステムは組むべきではないと思います。 確認していませんが、yarn なども似たようなことになっているようです。

runn と GitHub Actions でお手軽自動 API テストを導入してみた 😎

この記事は CADDi プロダクトチーム Advent Calendar 2024 8 日目の記事です。

はじめに

最近は CADDi で検索と戯れている伊藤 (@amaya382) です。 いろいろと豪勢な記事が並んでいるので今日はちょっとした小ネタを、少し前に導入して便利だったテストツールセットをゆる〜〜く紹介しようと思います。この記事を読むことで、最小限の労力で API テストがバチっとできる道筋が見えるかもしれません。

最近開発している検索用のバックエンドサービスではユニットテストとシナリオテストを中心に品質を担保することができていました。しかしながら、シナリオテストに頼る箇所が多く、実施頻度が限られるシナリオテストでは品質は保証できても手戻りリスクが大きい状態でした。特に実際のデータに依存するような検査項目やミドルウェア・データベースによる影響がユニットテストで捉えきることができず、悩みのタネでした。

この課題を解消するには「早く気づくための自動化が容易であること」と「テストにリアルなデータを利用すること」という点に着目し、API テストの模索を始めました。その他にも OpenAPI を Schema-first に運用していたためこのあたりの検証もまとめてできないかなーと検討した結果行き着いたのが今回紹介する runn です。

runn とは

既に解説記事がたくさん出始めていますが、2 年前に公開が始まったばかりの API テストツールです。YAML で HTTP、gRPC の呼び出しやさらには DB へのアクセスも簡単に取り回せるのが特徴です。ドキュメントやチュートリアルが充実しているのもチームで活用していくにあたって嬉しいポイントですね。ちなみに作者が日本の方のため、比較的日本語のドキュメントが整備されています。

正直機能紹介はこれらを見れば十分なため、この記事では実運用して便利だったポイントを中心に紹介していきます。

実際にローカル使ってみた

今回の環境

実例を紹介する前に、今回適用した環境を簡単に紹介します。主なコンポーネントREST API サーバーと Elasticsearch です。API サーバーのインターフェースは OpenAPI で管理されています。開発の依存を減らすために Schema-first な戦略を取っているため、API テストの中で「OpenAPI 定義に従っているか?」もチェックしたいところです。

API テストを書いてみる

さてここからは runn で実装したテストを紹介していきます。大まかな流れは以下のとおりです:

  • refreshTestData: Elasticsearch 上のデータのセットアップを行う。
  • searchArticlesBeforeCreation: API server で記事の検索を行う。今の段階では何もヒットしない。
  • createArticle: 記事を作成する。
  • verifyArticleOnElasticsearch: 作成された記事が Elasticsearch にあることを確認する。
  • searchArticlesAfterCreation: API server で記事の検索を行う。直前に作成した記事がヒットする。
desc: test my API server
runners:
  myApi:
    endpoint: ${MY_API_URL:-http://localhost:8080}
    openapi3: path/to/openapi.yaml
    skipValidateRequest: false
    skipValidateResponse: false
  elasticsearch:
    endpoint: ${ELASTICSEARCH_URL:-http://localhost:9200}
vars:
  articleId: article-01
steps:
  refreshTestData:
    include:
      path: ../.common/refresh-test-data.yaml
  searchArticlesBeforeCreation:
    myApi:
      "/v1/articles:search?q=nyan":
        get: {}
    test: current.res.status == 404
  createArticle:
    myApi:
      "/v1/articles":
        post:
          body:
            application/json:
              title: new title
              body: nyan
    test: current.res.status == 201
  verifyArticleOnElasticsearch:
    elasticsearch:
      "/articles/_doc/{{ vars.articleId }}":
        get: {}
    test:
      current.res.status == 200 &&
      current.res.body._source.title == 'new title'
  searchArticlesAfterCreation:
    myApi:
      "/v1/articles:search?q=nyan":
        get: {}
    test:
      current.res.status == 200 &&
      current.res.body.hits == 1 &&
      current.res.body.results[0].title == 'new title'

上から順に runn のどんな機能を使ってどんな検証がなされているか見ていきましょう。

runners

いきなり runn の便利さが詰まっている部分です。ここに HTTP クライアントを宣言することで、後の実行ステップで簡単に利用できるようになります。今回は REST の API サーバーと REST をメインのインターフェースとして Elasticsearch それぞれにクライアントを定義しています。注目してほしいのが myApi の定義にある openapi3 の指定です。ここで OpenAPI 定義ファイルを指定することによって、以降このクライアントを利用したときに自動的にリクエストとレスポンスが定義に従っているかを検証してくれます。わずか 1、2 行のことですが個人的に最も感動したポイントです。endpoint環境変数で外部から切り替えられるようにしています。

今回は利用していませんが、HTTP 以外にも gRPC や RDB クライアントも利用できます。YAML 上の最低限の記述だけでこれらのリクエストも扱えるのは他のツールにはあまり見られない特徴ですね。

runners:
  myApi:
    endpoint: ${MY_API_URL:-http://localhost:8080}
    openapi3: path/to/openapi.yaml
    skipValidateRequest: false
    skipValidateResponse: false
  elasticsearch:
    endpoint: ${ELASTICSEARCH_URL:-http://localhost:9200}

vars

テスト内で使う変数を定義できます。後ほど出てきますが、外部の JSON ファイルを読み込むこともできます。

vars:
  articleId: article-01

steps

ここにはテストの各ステップが記述されます。

steps:
  refreshTestData:
  ...

steps.refreshTestData

テストデータを Elasticsearch 上に準備するためのステップです。include 機能を使っており、これは外部の runn テストケースを呼び出すことになります。テスト環境のセットアップのような、複数のテストで共通化したい処理をまとめておくことができます。

refreshTestData:
  include:
    path: ../.common/refresh-test-data.yaml

通化されたテストケースも簡単に見ていきましょう。Elasticsearch は JSON 形式でインデックス定義 (RDB のテーブル定義のようなもの) を行います。ここでは indexSetting.json という外部の JSON にその定義をおいておき、テスト実行時に読み込んでリクエストボディとして利用しています。サービスからとテストケースからで共通の定義を利用できるのは良いですね。

insertTestData ではデータ投入のためにシェルスクリプトを実行しています。YAML から Elasticsearch へのアクセスを記述していってもよいのですが、ある程度の処理であれば外部のスクリプトにまとめると便利なことが多いです。test でこれらの処理の検証もしていますが次で紹介します。

.common/refresh-test-data.yaml

desc: refresh test data
runners:
  elasticsearch:
    endpoint: ${ELASTICSEARCH_URL:-http://localhost:9200}
vars:
  indexSetting: "json://indexSetting.json"
steps:
  createIndex:
    elasticsearch:
      "/article/":
        put:
          body:
            application/json: "{{ vars.request }}"
    test: current.res.status == 201
  insertTestData:
    exec:
      command: ./insert_data.sh ${ELASTICSEARCH_URL:-http://localhost:9200}
    test: current.exit_code == 0

steps.searchArticlesBeforeCreation

さて元のテストケースに戻りまして、今度は API サーバーに対して GET で検索リクエストを投げています。ただしこの時点でのデータではこの条件には何もヒットしないという想定です。runn では test を書いて検証できます。ここでは何も見つからないことを HTTP ステータスが 404 として検証しています。

searchArticlesBeforeCreation:
  myApi:
    "/v1/articles:search?q=nyan":
      get: {}
  test: current.res.status == 404

steps.createArticle

API サーバーに対して POST リクエストで新しい記事を作成しています。

createArticle:
  myApi:
    "/v1/articles":
      post:
        body:
          application/json:
            title: new title
            body: nyan
  test: current.res.status == 201

steps.verifyArticleOnElasticsearch

API サーバー経由のやり取りだけでなく、データベースへ直接アクセスして内部のデータを検証するステップも入れることができます。ここでは直前に作られた記事が API サーバーから Elasticsearch に期待通り永続化されているかを確かめるために、Elasticsearch に直接取得を試みます。Elasticsearch 用の HTTP クライアントを利用して GET した中身をチェックしています。

RDB を利用している場合は代わりに SQL を書いて検証することができます。

verifyArticleOnElasticsearch:
  elasticsearch:
    "/articles/_doc/{{ vars.articleId }}":
      get: {}
  test:
    current.res.status == 200 &&
    current.res.body._source.title == 'new title'

steps.searchArticlesAfterCreation

再度先ほどと同じ条件で API サーバーに対して GET の検索リクエストを投げています。直前に作成した記事がヒットするという想定で、HTTP ステータスに加えてヒットした記事の title を検証しています。

searchArticlesAfterCreation:
  myApi:
    "/v1/articles:search?q=nyan":
      get: {}
  test:
    current.res.status == 200 &&
    current.res.body.hits == 1 &&
    current.res.body.results[0].title == 'new title'

ここまでのまとめ

あとは runn をインストールして Elasticsearch と API サーバーを動かしておけばすぐにテストを実行できます。runn は golang 製なので バイナリ を拾ってくるだけでよいですし、各種パッケージマネージャー からも簡単にインストールできます。

さてこれで開発中に動かすのは余裕ですね。なんですが、チーム開発で確実に API テストを実施するにはやはりこれを CI に組み込んで自動化しなければなりません。というわけで次は GitHub Actions を CI として利用するまでを紹介します。

GitHub Actions 上で runn による API テストを実行する

全体としては、GitHub Actions 上で docker compose を使って Elasticsearch と API サーバーを立ち上げ、あとのステップで runn を実行するという形を取りました。

GitHub Actions

基本的には以下のように docker compose でサービスを立ち上げて次のステップで runn 用のアクションを指定しているだけです。開発環境で既に docker compose を利用していたことやパラメーターに制約があったことから GitHub Actions サービスコンテナではなくステップ内で docker compose を呼び出しています。

.github/workflows/test.yaml

name: Tests on Pull Request
on: pull_request

jobs:
  api-test:
    runs-on: ubuntu-latest
    steps:
      # (snip. build and push docker image)
      - name: Run services for API testing
        run: |
          IMAGE="my-api:${{ github.sha }}" docker compose up -d --wait
      - name: Run API tests
        uses: k2tzumi/runn-action@v0.120.0
        with:
          path_pattern: api-server/runn/**/*.yaml
          enable-run-exec-scope: true
          verbose: true

docker compose

何の変哲もない docker compose ファイルです。強いて言えば立ち上がりの遅い Elasticsearch を待つために healthcheck を入れていることくらいでしょうか。docker compose up -d するときに --wait をつけることでサービスが healthy になるのを待ってくれます。

compose.yaml

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.16.1
    # (snip.)
    healthcheck:
      test:
        [
          "CMD-SHELL",
          "curl -s http://localhost:9200/_cluster/health?pretty | grep status | grep -q -E '(green|yellow)'",
        ]
      interval: 3s
      timeout: 3s
      retries: 20
  myApi:
    image: ${IMAGE}
    # (snip.)
    depends_on:
      elasticsearch:
        condition: service_healthy
    healthcheck:
      test:
        [
          "CMD-SHELL",
          'node -e ''require("http").get("http://localhost:80/health", (r) => process.exit(r.statusCode === 200 ? 0 : 1));''',
        ]
      interval: 3s
      timeout: 3s
      retries: 20

さいごに

API テストを runn で実現しつつ GitHub Actions 上で動かしてみました。手軽に API テストケースを追加できるようになり、PullRequest の段階でユニットテストと同じくらい簡単に問題に気づけるようにもなりました。ヤッタネ!

どうやらいろんなポジションで人を探しているようです。気になる人は Twitter の DM でもなんでも気軽にどうぞ!(お久しぶりでもはじめましてでも)

VoicemeeterでUSBデバイス接続時の挙動を改善する

ふと思い立って前回のUSBデバイス版です。共通部分は省略。調べるのが面倒だったので使うIDとか一部適当です。

  1. USB接続イベントログを有効化
    • Event Viewerを開き,Applications and Services Logs > Microsoft > Windows > DriverFrameworks > UserMode > Operationalコンテキストメニューから Enable Log を選択
      • デフォルトでは無効になっているはず
  2. 対象のUSBデバイスのアドレスを確認
    • USB接続イベントログを有効化した状態で実際に接続すると Event ID=2100 (ID違うかも)としてログが吐かれるので,Event Properties > DetailsUserData > InstanceIdLifetimeId かも。適当)をメモ
  3. Task Schedulerの設定
    • Task Schedulerを開き,てきとうなディレクトリで Create Task
      • お作法をよくわかっていないが,Task Scheduler Library/Users を作っておいた
    • 以下を設定
      • General > Name: てきとう
      • Trigger > New: Begin the task=On an event
        • Settings > Custom > New Event FilterXMLタブから Edit query manually を有効化して以下を入力
          • 対象のUSBデバイスのIDはイベントログからメモったもの
            • & が含まれる場合はエスケープ (&amp;) が必要
          • <QueryList>
                <Query Id="0" Path="Microsoft-Windows-DriverFrameworks-UserMode/Operational">
                    <Select Path="Microsoft-Windows-DriverFrameworks-UserMode/Operational">
                        *[System[Provider[@Name='Microsoft-Windows-DriverFrameworks-UserMode'] and EventID=2100]] and *[UserData[UMDFHostDeviceRequest[InstanceId='【対象のUSBデバイスのID】']]]
                    </Select>
                </Query>
            </QueryList>
            
      • Actions > New: Action=Start a program Program/script=【作成したVBスクリプトのパス】
      • その他の設定はデフォルトでok

VoicemeeterでBluetoothデバイス接続時の挙動を改善する

Voicemeeter環境で特定のBluetoothイヤホンを接続したときにAudio Engineを自動でRestartさせるまでの備忘録です.

まず前提として,VoicemeeterのHardware Outにデバイスを登録しても再接続時に自動的に利用できるようにはならず(画面的には以下のAirPods Proのように赤文字のまま再接続されない),特に付け外しが多いBluetoothイヤホンの接続時に毎回手動でのRestartが必要で不便でした.一応,VoicemeeterにはA1もしくは任意のデバイス接続時にAudio EngineをRestartさせるオプション (Auto Restart Audio Engine (A1 Device), Auto Restart Audio Engine (All Devices)) がありますが,これらは対象のデバイスが接続されていないと定期的にRestartをかけてしまうため,未接続時には音声が飛びまくって使い物になりません.

方針

Bluetoothイヤホン接続イベント発生時にVoicemeeterのRestartをkickする.

環境

  • Windows11 10.0.22000 Build 22000
  • Voicemeeter Potato 3.0.2.2

詳細

  1. VoicemeeterをCLIから操作するためのツールをインストール
  2. vmcliのバックグラウンド実行用VBスクリプトを作成
    • vmcliを直接叩くとその度にコマンドプロンプト画面が出てしまうのを防ぐため
    •   Set ws = CreateObject("Wscript.Shell")
        ws.run "cmd /c 【vmcliのパス】 Command.Restart=1", vbhide
      
    • インストール先は任意
  3. Bluetooth接続イベントログを有効化
    • Event Viewerを開き,Applications and Services Logs > Microsoft > Windows > Bluetooth-Policy > Operationalコンテキストメニューから Enable Log を選択
      • デフォルトでは無効になっているはず
  4. 対象のBluetoothバイスのアドレスを確認
    • Bluetooth接続イベントログを有効化した状態で実際に接続すると Event ID=9 としてログが吐かれるので,Event Properties > DetailsEventData > bthAddr をメモ
      • 多分15桁の数値
  5. Task Schedulerの設定
    • Task Schedulerを開き,てきとうなディレクトリで Create Task
      • お作法をよくわかっていないが,Task Scheduler Library/Users を作っておいた
    • 以下を設定
      • General > Name: てきとう
      • Trigger > New: Begin the task=On an event
        • Settings > Custom > New Event FilterXMLタブから Edit query manually を有効化して以下を入力
          • 対象のBluetoothバイスのbthAddrはイベントログからメモったもの
          •   <QueryList>
                  <Query Id="0" Path="Microsoft-Windows-Bluetooth-Policy/Operational">
                      <Select Path="Microsoft-Windows-Bluetooth-Policy/Operational">
                          *[System[Provider[@Name='Microsoft-Windows-Bluetooth-Policy'] and EventID=9]] and *[EventData[Data=【対象のBluetoothデバイスのbthAddr】]]
                      </Select>
                  </Query>
              </QueryList>
            
      • Actions > New: Action=Start a program Program/script=【作成したVBスクリプトのパス】
      • その他の設定はデフォルトでok

これで指定のBluetoothバイスが接続されたタイミングで自動的にVoicemeeterがRestartされすぐに使えるようになりました.ちなみに,VBスクリプトの部分をいじれば「Bluetoothイヤホン接続時にはスピーカーをMuteする (ws.run "cmd /c 【vmcliのパス】 Bus[1].Mute=1", vbhide を追加)」みたいなこともできますね.便利.

本当はUSBデバイスでも同様のことがしたかったのですが,うまいことイベントを拾えないケースがありとりあえずBluetoothのみになりました.

【VGC2020】INC Apr. 最高/最終レート1841構築【エルジュラ雨 (Whimsicott&Duraludon Rain Team)】

はじめに

普段技術的な内容を扱っているブログですが突然のポケモンです。HGSSぶりにまともな対戦を再開したので、これからもたまにポケモン記事が増えるかもしれません。一応 (?) ポケモンだいすきクラブ・オーキ堂 のOBだったりします (所属していた期間はほとんど対戦してなかったんですが…)。

ポケモンは↓のTwitterアカウントで活動してます。ルビサファ時代からずっとカイオーガ推しです。 https://twitter.com/amaya383

An English version appears after the Japanese section.


INC Apr. お疲れさまでした。「思い切り上振れして最終2桁入ってみたい!」という目標だったのですが、33-9で最高/最終レート1841 最終21位を記録できたので構築 メモ を残そうと思います。WCS中止・JCSも開催困難そうというご時世ですが、(恐らく) ボーダーを超える結果を残せて嬉しいです!!!あくまで構築時の自分用メモに加筆調整しただけなので読みづらさはご了承を。

エルジュラ軸としてはかなりいい感じの構築に仕上がったと思うので環境が変わってしまう前に供養しようと思います。レンタルパも公開してますので興味がある方は使ってみてください。

f:id:amaya382:20200501121348j:plain

続きを読む

WSL2のshellがどうにも重かったのが解決できた話

何が重かったか

tmux+zshな環境を利用しているのですが、zshでコマンドなしでEnterを連続で押したときやtmux環境を保存・復元するtmux-resurrectが激重でした。特に後者は保存・復元にそれぞれ1分程度かかっていました (同程度のマシンで普通のlinuxであれば5秒とかからない処理です)。

ちなみに、shellから呼び出したプログラムの速度は特段遅く感じることはありませんでした。

原因と解決策

デフォルトでWSL2のshellにWindowsのPATHが含まれているのですが、これが原因だったようです。 ので、これをしない設定にしました。

以下の内容を /etc/wsl.conf に追記して再起動するだけです。

[interop]
appendWindowsPath = false

ちなみにこの設定は Build 17713 以降でのみ使えるようです。

(追記) 副作用

WSL2のPATHからWindows由来のものを消すと、当然ながらWSL2からWindows側のコマンドを直接叩くことができなくなります。たまに手動で利用する場合であればフルパス指定すればよいですが、頻繁に利用するプログラムが存在する場合や外部のプログラムからPATHを前提としたコマンド呼び出しがある場合は困ったことになります。 応急処置として、PATHが通った場所にシンボリックリンクを張ることで解決できます。

例1: VSCode

WSL2からWindowsにインストールされたVSCodeを直接開くには専用のシェルスクリプトを経由する必要がある

sudo ln -s '/mnt/c/Users/<user name>/AppData/Local/Programs/Microsoft VS Code/bin/code' /usr/local/bin/code

例2: Docker for Windows (w/ VSCode remote container)

VSCode remote containerはその起動時に docker-credential-desktop.exe がPATHにあることを前提としたスクリプトが実行される (最近のバージョンでは不要になったかも?)

sudo ln -s '/mnt/c/Program Files/Docker/Docker/resources/bin/docker-credential-desktop.exe' /usr/local/bin/docker-credential-desktop.exe

参考