[{"content":"はじめに Dify の GitHub や Forum や Discord で、『クラウド版の Dify、落ちてない？』系の質問にたまに出会います。\nで、そういうのを見て、公式のステータスページはいつ見てもずっと緑だし、実際の稼働状況わかんないよね、そうだよね…… と思ったことが一度や二度ではなくなってきたことに加えて、ちょうど Claude Opus 4.6 が出て何かさせてみたい気持ちが出てきたので、Claude Code に隅から隅まで任せて 非公式のステータスページ を作りました。ソースコードは GitHub で公開 しています。\nDify Cloud Status (Unofficial) 2 月下旬から動かし始めて、まるまる一か月安定して動いてくれたので、改めて簡単に紹介です。\n……CDN を通していないので、アクセスが増えると GitHub Pages の転送量制限的なアレで落ちるかもですが……。\n仕様 クラウド版の Dify を対象に、15 分ごと に各種チェックを行っています。\nチェック項目 チェックの内容 Web UI cloud.dify.ai に HTTP で GET して、ステータスコード 200 が返ることを確認 API 最小構成のアプリを API で実行して、アプリが動作することを確認 Sandbox テンプレートノードを含むアプリを API で実行して、テンプレートが動作することを確認 Plugin LLM ノードを含むアプリを API で実行して、モデルプラグインが動作することを確認 Knowledge Retrieval ナレッジ API を実行して、ナレッジに対するベクトル検索で結果が返ることを確認 Knowledge Indexing ナレッジ API を実行して、ナレッジにドキュメントをアップロードし、インデキシングのキューが処理されることを確認（Sandbox と Professional の両プランで） Webhook Trigger Webhook トリガをもつワークフローを実行して、処理されることを確認（Sandbox と Professional の両プランで） チェックに使っているアプリは、DSL ファイルとして リポジトリに公開 しています。興味がある方は持って行ってください。\nなお、Knowledge Indexing と Webhook Trigger で Sandbox プランと Professional プランの両方をチェックしているのは、Dify のクラウド版がプランによってこれらを含む一部の処理の優先度を変えているためです。Professional プランのアカウントでは待ち時間ナシなのに Sandbox プランでは長時間待たされる、な状況が起こりうるため、念のため両方チェックしています。ただし、手持ちの Professional プランの契約期間が切れたあとのことは考えていません。\nほとんどのチェックが API の動作に依存しているので、例えば API サーバそれ自体が落ちているとチェックが全滅する可能性がありますが、その辺はもう仕方ないものとして割り切っています。\n使い方 ステータスページを見る トップページ には、現在のステータスと、過去 90 日分のステータスのグリッドが表示されています。グリッドは 1 日で 1 マスで、その日のチェックが全部緑（Up）なら緑、赤（Down）が全体の 50 % 以上なら赤、それ以外は黄色（Degraded）で表示されます。\n30 日分、14 日分、7 日分、3 日分、24 時間分の表示に切り替えれば、1 時間ごとのグリッドも見られます。\n各チェック項目をクリックすると詳細ページに飛べて、日ごとの結果やレスポンスタイムのグラフを確認できます。\n通知を受け取る 15 分ごとのチェックで状態に変化（2 回連続の Down またはその後の復旧）があると、GitHub の Issue #2 にコメントが自動で投稿されます。この Issue で Watch を有効にしておけば、GitHub から通知を受け取れます。\n実装 あまり複雑なことはしておらず、ポイントといえば次の 2 点くらいです。\ncron-job.org が、15 分ごとに GitHub Actions をキックします GitHub Actions のなかで、チェックの実行と Issue へのコメントの投稿、静的サイトのビルドとデプロイ（GitHub Pages）を行います もともとは GitHub Actions のスケジュール機能で全部やるつもりでしたが、試してみたらぜんぜん期待した頻度で動いてくれなかったので、外部からつつく形にしました。依存サービスを増やしたくなかったのですが仕方なしです。\n15 分、という間隔の根拠は、ステータスページのためのテスト自体が Dify 側への過剰な負荷にならないこと、Sandbox プランの API 制限（月 5,000 回）の中でやりくりできること、あたりを考慮した結果です（Dify の ToS を確認したうえで、複数のアカウントを併用しています）。\nDify 側でのアプリづくり以外は、CLAUDE.md のメンテナンス自体も含めて全部 Claude Code におまかせしました。CLAUDE.md の作成・修正を人間ではなく AI に任せるのは悪手ともいわれますが、そもそもプロジェクトの規模が小さくてアーキテクチャもシンプルなので、精緻なチューニングが必要になるほどのものではなく、人間の手間を省く方向を優先しています。\nおわりに Dify の非公式ステータスページを紹介しました。作る過程も含めておもしろかったです。\nあと、感覚としてずっと持っていた、『じつはちょこちょこ落ちている』が明確に可視化されたのは収穫でした。『ちょこちょこ』というには日本時間の平日日中帯に黄色と赤が多い気もしますが、まあ、クラウド版はそういうものだとわかって使うべきものなので……。\nあくまで非公式の個人プロジェクトでしかないので、精度や継続性の保証はまったくありませんが、『Dify のクラウド版、落ちてない？』 なときには、公式のステータスページ よりも今回のコレを見てもらったほうが、リアルな状況が可視化されているはず…… です。\nそのうち Cloudflare を手前に置きたいですね。\n","date":"2026-04-01T15:20:10Z","image":"/archives/5557/img/image-447.png","permalink":"/archives/5557/","title":"Dify Cloud の非公式ステータスページを作った"},{"content":"はじめに Dify の開発は GitHub 上で行われているので、リリースされていない開発中の新機能も、開発中のブランチを手元で何やかやすると動かして試せます。\nというわけで、開発が進んでいる機能の中から、おもしろそうなヤツを 3 つさらっと紹介です。\nHumal in the Loop（HITL） AgentBox Vibe Workflow いずれも開発中のものを勝手に動かしているだけなので、最終的にリリースされるモノとは大きく異なる可能性があります（さらにいえば、リリースされることも保証されていません）。\nとはいえ、Human in the Loop（HITL）は、おそらくまもなく（今日明日にでも）リリースされるでしょう。\nHuman in the Loop（HITL） ワークフローの途中で処理をいったん止めて、人間からの入力を待ってから次に進む機能です。続行前に承認を求めたり、入力に応じて処理を分岐させたりできます。たぶん 1.13.0 でリリースされます。\nこの機能は、ワークフローの編集画面で、あたらしく Human Input ノード を配置すると使えるようになります。こんなイメージです。\n試した段階では、人間に入力を求める方法として、次の二つが用意されていました。\nアプリの実行画面にフォームを直接表示する メールで任意の宛先にフォームの URL を送信する 人間に提示する UI は、上の画面のように、変数を参照してなにかを表示するほか、テキスト入力欄を表示させてユーザからの入力を受け取るようなことも可能です。また、クリックに応じてその後の処理を分岐させられるボタンを定義できます。『続行 / キャンセル』だったり、『処理 A / 処理 B / 処理 C / キャンセル』だったり、便利に使えそうですね。\nフォームをアプリの実行画面に直接表示する方法では、下の画面のように、実行中に UI が割り込んで表示される形です。\nメールを送るように設定すれば、指定した宛先にはこのようなメールが届きます。メールの件名や宛先、本文は、これもまた変数などを使って自由に記述できます（ただし本文は Markdown フォーマットではなく HTML でベタ書きが必要です）。\nアクセスするとこんな感じの画面が開きます。いずれかのボタンで応答することで、完了画面に遷移します。\nAgentBox ひとことでいうと LLM 向けのサンドボックス環境のようなもので、ChatGPT の Code Interpreter をひとまわり自由にした感じのやつです。\nサンドボックス環境には、Docker コンテナ、E2B（AI 向けのサンドボックス環境を提供するプラットフォーム）、ローカルホストのいずれかが利用できます。\n組み込みで新しいツール Code Interpreter が実装されており、例えば Agent ノードにこのツールを渡すことで、LLM がサンドボックス環境の中で自由に Bash を触ってコマンドやコードを実行できるようになる感じです。\nまた、ツール経由の実行だけでなく、あたらしく Command ノード も追加され、これ単体でもサンドボックス環境の中で任意のコマンドを実行できるようになります。手書きしてもよいですし、LLM にコマンドを生成させてそれを変数で渡すのももちろんアリです。\nサンドボックス環境に Docker コンテナを選択した場合、このコンテナは フローの実行ごと に使い捨てられます。ノードの実行ごとではない のがミソで、つまり、前のノードで何かをインストールしたりファイルを作ったりするコマンドを実行した場合、後のノードでもその状態が維持されていることになります。\nコンテナイメージにはデフォルトでは ubuntu:latest が使われますが、LLM が使いそうな言語やパッケージをあらかじめ詰め込んだ dify-agentbox なるイメージ（リポジトリ）が新しく提供されます。これを使うことで、LLM が Playwright だったり PDF 操作系の何やかやだったりを特別な準備なくいきなり使えるようになります。自前でカスタマイズしたイメージを使うのも簡単そうです。\n併せて、そもそも LLM ノードが廃止されて Agent ノードに一本化されていたり、プロンプト中に @ キーからサクサクとツールを足せるようになっていたり、会話の内容からパラメータを抽出する処理をツールのパラメータ欄ひとつでできるようになっていたりと、いろいろ変わっている気配があります。最終的にどうなるか楽しみです。\nVibe Workflow いわゆるバイブコーディングのワークフロー版です。自然言語で指示をすることで、ワークフローが勝手にできあがるようになります。\nこの機能は、編集画面で Go to Anything を開いたあと、コマンド /banana で起動できます。ばなな。\n生成されたワークフローは右側でプレビューされ、いいやつができたらそのまま編集画面に持ち込めます。各ノードの設定（例えば LLM ノードのプロンプト）も含めて生成されるようです。\nできあがるワークフローの精度は、指示にもモデルにも強く依存しそうですが、現段階では、ビジネスレベルの要件をフローに落としてもらう機能というよりは、多少はフローの具体的な処理の流れが指示できる状態でたたき台を作ってもらう機能と捉えるくらいがよさそうな感触でした。\nおわりに 開発途中の機能を少しだけ紹介しました。開発途中なので当然バグも多いですが、新しい機能を精力的に増やしていく開発方針は個人的には歓迎しており、引き続き実装面でもコミュニティサポート面でもコントリビューションしていきたい気持ちです。\nリリースが楽しみですね。HITL はこのエントリを書いている間にあれよあれよとリリース準備が進んでいるので、まもなくみなさんのお手元でも使えるようになるでしょう。\n","date":"2026-02-10T13:55:20Z","image":"/archives/5530/img/image-441.png","permalink":"/archives/5530/","title":"Dify の開発中の新機能： Human in the Loop、AgentBox、Vibe Workflow"},{"content":"はじめに Dify の 1.10.0-rc.1 で、新しい トリガー機能 がリリースされました。\nこれは、簡単にいえば、指定したスケジュール や Webhook の受信、SaaS 上での何らかの動き などを契機に、イベントドリブンなワークフローの実行 ができるようになる機能です。\n先日リリースされた ナレッジパイプライン とあわせて今年の目玉機能のひとつとされていたもので、開発目線では 実装へのコントリビューションが積極的に募集され進捗も公開されていた 点がめずらしいケースでした。\nそれはそれとして、1.10.0-rc.1 時点 の情報を、主に SaaS との連携 を中心にまとめます。リリース名の通り現時点では RC 版 であり、正式版では仕様が変わる可能性はもちろんあるので、その点はご了承ください。\nTL; DR スケジュールトリガ は、Dify 以外に必要なものがないため、利用はとても簡単です。Webhook トリガ も、送信側に Dify へのリーチャビリティがありさえすればよく、利用は簡単です。\n一方で、SaaS と連携したトリガ を利用するには、Dify が SaaS 側からの POST リクエストを受け取れる必要 があります。したがって、OAuth の認証も考慮すると、多くの場合で Dify 側のエンドポイントを正規の SSL 証明書で HTTPS 化してインタネットに公開する ことが求められるため、セルフホスト環境****での利用には一定のハードルがある と考えたほうがよいでしょう。他方、クラウド版 では利用のハードルは低く、比較的気軽に使えると考えられます。\n現時点では、1.10.0-rc.1 をセルフホスト すれば、いずれの機能も試用できます。ただし、SaaS と連携したトリガを試したい場合は、プラグインを自分でソースコードからパッケージすること と、前述のとおり 正規の証明書を取得して Dify を HTTPS でインタネットに公開すること が必要になる場合があります。\n前提： トリガ機能の種類と仕組み トリガ機能には、大まかには次の三つの種類があります。\nスケジュール機能 Webhook 機能 SaaS 連携機能 スケジュール機能 スケジュールトリガ では、N 時間ごと・日次・週次・月次から選択するか、Cron 形式でのスケジュールを設定できます。\n設定すると、worker_beat コンテナ（Dify の 1.7.0 から追加されています）でスケジュールが管理されます。コンテナ名の通り、このあたりの実装は Cerely Beat です。\n指定した時間になると worker_beat コンテナからタスクがキックされ、あとは Cerely の Worker たる worker コンテナが後続の処理を担います。\nこれは組み込みの機能で、プラグインなしで動作 します。Dify 外への接続要件もなく、非常に気軽に使える、便利で強力な機能です。\nWebhook 機能 Webhook トリガ を設定すると、ノードごとに一意の URL が発行されます。\nこれを外部から叩くことで、後続の処理が流れ始める仕組みです。この URL へのリクエストは、api コンテナが受け取っています。\nこれも組み込みの機能で、プラグインなしで動作 します。Webhook の送信側から Dify の URL に到達できさえすればよく、curl や Invoke-WebRequest などを使えばテストも簡単です。例えば企業のプライベートネットワーク内などでも便利に利用できるでしょう。\nSaaS 連携機能 外部の SaaS で発生したイベント をトリガにできる機能で、動作には SaaS ごとに専用のプラグインが必要 です。\nプラグインごとの設定で、Dify 側で 目的のイベントに応じた Subscription を作成 すると、その Subscription ごとに一意の URL が発行されます。この URL を SaaS 側の Webhook 送信機能に（手動または自動で）登録することで、Dify に SaaS 側のイベントが届くようになります。この URL へのリクエストは、api コンテナが受け取っています。\nその仕様から、つまり、\nSaaS 側から Dify 宛に HTTP でイベントを POST してもらう ことで動作するため、Dify 側のエンドポイントをインタネットに公開する必要がある といえます。さらに、細かい要件は SaaS 側の仕様にも依存するものの、OAuth での認証プロセスも含めると、Dify 側のエンドポイントは 正規の SSL 証明書で保護された HTTPS であることが要求されることも多いでしょう。\nこの意味で、もともとインタネット上でサービスされている クラウド版の Dify では特別な工夫なく使えるため 利用のハードルは低い といえます。一方で セルフホスト環境 では、そもそもの 環境の用意それ自体に技術的なハードル がありますし、加えて、特に企業でのユースケースではインタネットにエンドポイントを露出させることに対して セキュリティポリシなどの管理面や運用面でもハードル があることが予想されます。\nが、いずれにしても、うまく活用できれば強力な機能であることは確かです。\n実装面を踏み込んで理解したい場合は、トリガプラグインの開発を解説した公式ドキュメント を参考にできます。\n環境の準備 今回は、SaaS 連携を試す ことを目的に環境を準備します。\n1.10.0-rc.1 をチェックアウト したうえで、標準の docker-compose.yaml の 隣 に、今回は次の docker-compose.override.yaml を用意しました（ドメインは example.com に置き換えています）。\nservices: api: environment: \u0026amp;env CONSOLE_API_URL: https://dify.example.com CONSOLE_WEB_URL: https://dify.example.com SERVICE_API_URL: https://dify.example.com TRIGGER_URL: https://dify.example.com APP_API_URL: https://dify.example.com APP_WEB_URL: https://dify.example.com FILES_URL: https://dify.example.com INTERNAL_FILES_URL: https://dify.example.com worker: environment: *env worker_beat: environment: *env plugin_daemon: environment: FORCE_VERIFYING_SIGNATURE: \u0026#34;false\u0026#34; ENFORCE_LANGGENIUS_PLUGIN_SIGNATURES: \u0026#34;false\u0026#34; nginx: environment: NGINX_HTTPS_ENABLED: \u0026#34;true\u0026#34; ポイントは以下あたりです。\napi サービスで、*_URL 系の指定を正規の HTTPS な URL にしています アンカ（\u0026amp;）とエイリアス（*）で、worker と worker_beat にも同じ設定をしています plugin_daemon サービスで、自分でパッケージしたプラグイン（後述）をインストールできるように、次の二つを false にしています FORCE_VERIFYING_SIGNATURE： パッケージの署名の検証を強制する ENFORCE_LANGGENIUS_PLUGIN_SIGNATURES： author が langgenus のプラグインは署名を必須にする nginx サービスで、HTTPS を有効化しています なお今回、.env ファイルは .env.example をコピーしたまま、何も手を加えずに放置です。.env ファイルをいじらなくても、上記のように docker-compose.override.yaml に書けばそれが優先されます。この手法は、.env ファイルの差分を気にしなくてよくなるうえ、環境変数以外の細かいカスタマイズも一元管理できるので、なかなか楽です。\nファイルができたら、あとは docker compose up -d しておしまいです。試した範囲ではイメージの差し替えは不要でした。\nプラグインの準備 現時点ではトリガ機能は正式版ではないので、プラグインはまだマーケットプレイスでは公開されていません。このため、SaaS 連携のトリガを使いたい場合は、開発中のプラグインのソースコードを持ってきて、自分でパッケージします。\n開発中のプラグインは、だいたい dify-official-plugins か dify-plugin-sdks の Feature ブランチにあることが多く、今回もそうでした。本エントリ公開時点では以下が入っています。\nlanggenius/dify-official-plugins： feat/triggers ブランチの triggers ディレクトリ GitHub Trigger（今回試したもの） Linear Trigger Notion Trigger RSSHub Trigger Typeform Trigger langgenius/dify-plugin-sdks： feat/trigger ブランチの python/examples ディレクトリ Dropbox Trigger GitHub Trigger Gmail Trigger（今回試したもの） Google Drive Trigger Lark Trigger Slack Trigger Telegram Trigger これを Dify のプラグイン開発の通常のお作法でパッケージします。\ndify plugin package ./github_trigger dify plugin package ./gmail_trigger 今回は Gmail と GitHub を試したかったので、dify-official-plugins から GitHub Trigger を、 dify-plugin-sdks から Gmail Trigger をそれぞれパッケージしました。あとは Dify にインストールするだけです。\nなお、Gmail Trigger はそのままでは動かず、修正が必要でした。修正は PR で送っている ので、マージ前に手元で試したい場合は差分をあててからパッケージしてください。\nGmail と GitHub 以外のプラグインは細かい確認はしていませんが、manifest.yaml の meta.minimum_dify_version をいじらないとインストールができないパタンが多そうです。また、requirements.txt の dify_plugin は、最低でも 0.6.0b13 以降にしておくと安心です。どのプラグインも成熟しているとはいえないため、すんなり動けばラッキーくらいの気持ちで覚悟を持つとよいと思います。\n試行： Gmail 連携 現時点の実装では、Gmail 用のトリガは次の 4 種類のイベントに対応しているようです。\nメッセージの受信 メッセージの削除 メッセージのラベルの追加 メッセージのラベルの削除 今回はこのうち、メッセージの受信 を試しました。プラグインの README がけっこう細かいので、それを見ながら設定するとどうにかなりますが、大まかには構成のポイントは次のあたりです。\nGmail の API だけでなく、Cloud Pub/Sub の API も有効にする Cloud Pub/Sub の Topic に Gmail のイベントが流れるようにする Cloud Pub/Sub に Push な Subscription を作って、Dify の URL を入れる Dify 側の Subscription の設定は例えばこんな感じです。Callback URL 欄に表示される URL を、Cloud Pub/Sub の Push 先にします。Label IDs は、今回は INBOX（受信トレイ）だけ選択しています。\nCloud Pub/Sub 側の設定例。Push の Subscription を作っています。\n動作確認用のワークフローも作りました。トリガされたことがわかりさえすればよいので、トリガノードの出力を Template でダンプするだけのやつです。\nGmail にメールが届くと、トリガが実行されます。届いたメールと、それによって動いたワークフローのログがこんな感じです。\nいい感じですね。\n試行： GitHub 連携 現時点の実装では、GitHub 用のトリガは例えば以下のようなイベント（全部で 50 種類）に対応しているようです。\nIssue の作成、コメントの追加 Pull Request の作成、レビュ、レビュコメントの追加 スターの追加 リリースの追加 ほか 今回は、シンプルに Issue の作成 を試しました。プラグインの README では PAK での認証が推奨されていましたが、GitHub 側での Webhook の作成がコケたので、OAuth にしています。\nDify 側の Subscription の設定はこんな感じです。リポジトリを選択して、受け取りたいイベントを選択します。今回は Issue 関係を選んでいます。\nSubscription ができると、GitHub 側に設定が走り、指定したリポジトリに Webhook が自動で構成されます。投げ先の URL だけでなく、Dify 側で設定したとおりに投げるイベントの種類も自動で構成されました。\nこの状態で、指定したリポジトリに Issue を作成しました（後から画面を撮ったのでクローズ済みに見えますが、もちろん、作成しただけでイベントは発火します）。\nワークフローがトリガされたことがわかります。\nGitHub 側でも、Webhook の送信履歴を確認できます。\nいい感じですね。\nおわりに 個人的に、SaaS 連携のトリガには Pull 型の実装、つまり Dify への インバウンド通信が必要ないアーキテクチャを期待 していたのですが、今のところは今回紹介したように Push 型 の実装のようでした。仮に Pull 型を目指した場合、Dify 側でサブスクライバ相当のプロセスなりスレッドなりを常駐させる必要も出てきそうなので、オーバヘッドを回避したか、アーキテクチャをシンプルにしたか、何か理由はあるものとは思います。\nそんなわけで、正式版がリリースされたらクラウド版ではたいへん便利に使えると思いますが、Dify は残念ながら（運用負荷を飲んででも）セルフホストしたほうが機能が安定する傾向もあるので、セルフホスト環境での利用ハードルがすこし高い点は惜しいポイントです。\nとはいえ、未来永劫このままということが決まっているわけでもなく、エンハンスは今後も続いていくでしょうし、引き続きの進化には期待したいですね。\n","date":"2025-11-03T12:45:00Z","image":"/archives/5490/img/image-431.jpg","permalink":"/archives/5490/","title":"Dify のトリガー機能を試す（Gmail / GitHub 連携）"},{"content":"はじめに Dify の今年の目玉機能のひとつ、ナレッジパイプライン（Knowledge Pipeline）が、昨日、Dify の 2.0.0 のベータ版（beta.1）の機能のひとつとしてリリースされました。\nこの機能、ベータ版の公開前から勝手に興味をもって個人的に実際に動かして触ったりバグをちょこっと直したり機能をちょこっと足したりしていたので、ベータ版の公開に合わせて、簡単に紹介します。\nただし、以下の点、ご了承ください。\nベータ版より前の開発途中のソースコードを手掛かりに、公式のドキュメントなどが何もない段階で、自分の体験や理解・解釈に基づいてまとめたものです 開発途中ならではのバグにより実際には動かせなかった部分を推測で補っているところもあります したがって、本エントリの内容は正式なリリースの内容とは一致しない可能性があります 概要： ナレッジを作るためのワークフローのようなもの ナレッジパイプライン は、ぼくの理解では、ナレッジを作るためのワークフロー（のようなもの）です。\nとても簡単にいうと、以下のようなことを ワークフロー形式でツールや分岐も駆使しながら自分で好きなように組める ということです。\nナレッジの元になるデータをどこからとってきて そこから情報をどのように抽出したり整形したりして どのようにチャンクにわけて ベクトル DB にどのように埋め込むか 実際の画面はこんな感じです。ワークフローやチャットフローを作ったことがある方々にとってはとても見慣れた雰囲気のやつです。詳細は後述しますが、ナレッジごと にこの編集画面を操作できるようになります。\n極端な例として、見た目（？）重視で作ったので動きませんが、アプリのワークフローっぽく条件分岐やループや LLM ノードやツールを使った複雑なものも組めます。\nウォークスルー： 実際の作り方と使い方 踏み込んだ説明の前に、ナレッジパイプラインの作り方と使い方を、スクリーンショットで簡単に紹介します。こういう見た目でこういう操作感のモノができる、という感触をふんわり把握いただけると、後述する細かい説明もたぶんわかりやすくなる…… と思います。\n繰り返しますが、画面は開発中のものなので、リリース版とはおそらく少し異なります。流れは一緒のはずです。\nナレッジの新規作成 パイプラインはナレッジごとに作るものなので、まずはナレッジを作成します。\nKnowledge ページに進むと、今まで通りの Create Knowledge に加えて、新しく Create from Knowledge Pipeline があります。今まで通りのナレッジで充分な場合は、前者を選択すればパイプラインのないナレッジが引き続き作れます。今回は後者です。\nCreate from Knowledge Pipeline を選択して進むと、パイプラインのテンプレートを選ぶ画面に進みます。……が、Blank knowledge pipeline 以外はなにもありません。\n開発中だからないのか、あるいはコミュニティ版はこういうものなのか、どちらかわかりませんが、実装上はテンプレートを取得する API のエンドポイントは用意されていますし、Enterprise 版ではカスタムテンプレートが作れそうです。コミュニティ向けにも何か公開されるのでしょうか。\nさて、おとなしく Blank knowledge pipeline を選んで進むと、パイプラインの作成画面に入ります（初期状態がリリース版と違いますが、やることは一緒です）。\nパイプラインの編集 Add data source から、ナレッジのもとになるデータを取得するノードを追加します。ワークフローでいう開始ノードみたいなものですね。見た目で察した方も多いと思いますが、データソースノードの種類もプラグインで増やせます。\nとりあえず、従来通りのファイルアップロードを受け付けるようにします。\nFile データソースを配置します。受け入れる拡張子を設定できますが、デフォルトで進めます。出力は File 型のオブジェクトです。次の Dify Extractor に接続します。\n次の Dify Extractor は、ファイルからテキストと画像を抽出するノードで、従来のナレッジの裏側で動いていた機能がプラグインとして移植されたもの（にちょっと機能が増えたもの）です。\nワークフロー向けの Doc Extractor と違って、ファイル内の画像群が images として別に出力されるようです。この出力を、例えば LLM ノードの Vision に渡して説明させたテキストそれ自体をナレッジに入れる、など応用もできそうですね。\n今回はいったん入力に File ノードからの出力を渡すだけにしておきます。\n続けて General Chunker ノードです。従来のナレッジでもチャンキングの方法を選ぶ画面がありましたが、アレに相当する部分です。\nこのノードは、名前の通り汎用モード用のチャンクを作る役割を持ちます。今回は親子モードを使いたいので、Parent-child Chunker に置き換えました。入力は Dify Extractor ノードの出力を渡します。\n最後が Knowledge Base ノードです。ワークフローでいう終了ノードみたいなものです。今回は前のノードから親子型のチャンクを流し込みたいので、Chunk Structure を Parent-child にして入力を設定します。\nほか、インデキシング方法や埋め込みに使うモデル、検索方法を指定します。\nできたら Test Run しましょう。ファイルをアップロードして処理を進め、いい感じだったら Publish して完成です。\n余談ですが、Publish as a Knowledge Pipeline というメニュが見えます。Enterprise 向けの、パイプラインをテンプレート化できる機能のようです。\nドキュメントの追加 パイプラインを保存できたら、以降はナレッジの Documents メニュからドキュメントを追加できます。\nChunk Settings は、今回のパイプラインだと何もありません。後述しますが、実行時にユーザからの入力を受け付けるように構成すると、ここに表示されるようになります。\nこのあとの埋め込み処理や、完了後のドキュメントの見え方は、今までとさほど変わりません。\nパイプラインの編集 作成済みのパイプラインは、左ペインの Pipeline メニュから編集できます。\n応用： 複数のデータソースの利用 ひとつのパイプラインに、データソースノードは複数配置できます。\n例えば、File の横に Google Drive ノードを追加して、認証を済ませてから Run this step をすると、Google Drive 上のファイルが一覧されます。ナレッジに足したいファイルが選べそうな雰囲気です。\nあとは好きなように後ろにノードをつなげて、最後の Knowledge Base ノードまで流れるようにすればヨシです。とても単純に組むならこんな感じでしょうか。ソースによってその後の処理を変えたい場合は、素直に Knowledge Base ノードまでの流れを 2 本並べる形でもよさそうです。\nこのようにデータソースを複数配置すると、ユーザ目線では、ドキュメントを追加する画面でソースの種類を選べるようになります。Google Drive の場合は、ナレッジに追加するファイルを選択して先に進む形です。\n応用： ユーザ入力フィールド パイプライン側で設定をすれば、ドキュメントを追加する画面に、ユーザからの入力を受け付ける欄を追加できます。ワークフローでいう開始ノードの変数みたいなもので、従来のナレッジの詳細設定画面のように使えます。\nユーザ入力フィールドの設定場所は、パイプラインの編集画面の上部の Input Field です。特定のデータソース向けのフィールドと全データソースで共通のフィールドを設定でき、値はパイプライン中で参照できます。\n上の画面のように、チャンキングの際のデリミタやチャンク長さをユーザが指定できるようにしたり、ユーザの選択に応じて IF/ELSE ノードで後続の処理を切り替える目的などで利用できます。\nもうちょっと詳しく： ステップごとの考え方 ナレッジパイプラインは、ここまでで紹介したように、従来のアプリのワークフローのように、データソースノードを起点にさまざまなノードやツールを自由に組み合わせて作成できます。\nもう一歩踏み込んだ理解をするには、ナレッジパイプラインを大きく 次の 4 つのステップ に分けて考えるとよさそうです（勝手に分類して命名しただけなので、正式な何かがあったらごめんなさい）。\n1️⃣ データソース ナレッジのもとになるデータを何らかのソースから取得するステップ 例： ユーザからのアップロード（従来と同じ） 例： 外部のオブジェクトストレージやオンラインストレージからのダウンロード 例： 外部のコラボレーションツールの参照 例： Web ページの取得 2️⃣ 抽出・クレンジング 前のステップで取得したファイルやコンテンツから、テキストを抽出したり、整形したり、クレンジングしたりするステップ（いわゆる ETL に相当） 例： いつも通りのテキスト抽出（従来と同じ） 例： Unstructured や MinerU などサードパーティの仕組み 3️⃣ チャンク 前のステップでできた文字列をチャンクに分けるステップ 例： 汎用モード（従来と同じ） 例： 親子モード 4️⃣ ナレッジベース チャンクを保存するステップ 以下、各ステップを簡単に紹介します。\nデータソース： ナレッジのもとになるデータを取得する ナレッジのもとになるデータを、どこかからどうにかして取ってくるステップです。\nパイプライン上では、ワークフローでいう開始ノード相当 のものとして配置できます。ただし前述のとおり、ひとつのパイプラインに複数のデータソースノードを配置できます。\n従来の Dify では、ナレッジの元ネタには手動アップロードか Notion か Web クローリングのいずれかしか選べませんでしたが、ナレッジパイプラインでは、データを取得する機能それ自体 が データソースプラグイン として提供されるため、プラグインをインストールすれば選択肢を増やせます。\nぼくが触っていた段階では、公式プラグイン としては以下が開発中でした（仕分けはぼくが勝手にしたものです）。今後も増えるでしょうし、パートナ企業さんやコミュニティからの公開も期待できるでしょう。なお、従来通りの 手動でのファイルのアップロード を受け付けるノードは、プラグインではなく標準でバンドルされています。\nオブジェクトストレージ系 Amazon S3 Azure Blob Google Cloud Storage オンラインストレージ系 Box Dropbox Google Drive Microsoft OneDrive コラボレーションツール系 Confluence GitLab GitHub Notion Microsoft SharePoint Web クローラー系 Bright Data Firecrawl Jina Reader Tavily これ以外だと、例えば SMB とか NFS とか、Teams とか Slack とか、そんなのにも需要が生まれそうです。\n抽出・整形： 情報を抽出してクレンジングする データソースから取得されたデータをもとに、ナレッジに登録する情報を作るステップです。\nこのステップ用に、従来のナレッジが持っていた機能のプラグイン版として、ファイルからテキストや画像を抽出する Dify Extractor、URL やメールアドレスを削除する Dify Cleaner が提供されています。最小限のパイプラインであれば、これらだけでも充分に機能します。\n……が、パイプラインの価値 は、上の図のように、データに対する処理 を ==さまざまなノードやツールを駆使してワークフローっぽく自由に組める== ところにあります（上の図は見た目重視で組んだだけのイメージですが）。\nつまり、ステップ全体としては、\n前段のステップからファイルオブジェクトまたはテキストや画像などのデータを受け取って 後段のステップにテキストデータとして渡す ということさえできればよいので、ワークフローのように条件分岐やループを使ったり、LLM で要約や整形などの前処理をさせたり、コードブロックでキーワードの置換やマスクなど機械的な何かをしたりと、さまざまなノードやツールをパイプラインに組み込んで、必要なだけ整形なりクレンジングなりの作り込みができることになります。\nチャンク： テキストをチャンク化する 前のステップから渡されたテキスト情報をチャンクに分けるステップです。\n公式プラグインとして、次の三つが提供されていました。\nGeneral Chunker（汎用チャンクを作成） Parent-child Chunker（親子チャンクを作成） Q\u0026amp;A Chunker（Q\u0026amp;A チャンクを作成） 汎用チャンクと親子チャンクは以前からあったものと同じです。ノードとして配置すると、前述のとおり、設定画面でチャンク長やデリミタなどを指定できるようになります。\nQ\u0026amp;A チャンクは、質問と回答のペアをチャンク化したものです。従来のナレッジには LLM を使って Q\u0026amp;A 形式に整形させる選択肢がありましたが、Q\u0026amp;A Chunker はそれ ではなく、単に質問列と回答列を含む CSV ファイルを受け取って行ごとにチャンク化するだけのもののようです。\nチャンクの種類自体は汎用・親子・Q\u0026amp;A の三種からは増やせませんが、例えば、特定の構造のテキストに特化したチャンキングを行えるプラグイン の実装なども考えられそうです。\nナレッジベース： チャンクを保存する 作成されたチャンクをナレッジに保存するステップです。\nここでは、前述のとおり、保存するチャンクを指定するほか、検索設定も行えます。Chunk Structure を選択すると、対応する型の変数が Input Variables で選べるようになります。\nこのノードは、パイプラインに必ずひとつ以上配置する必要があります。組み込みのノードであり、プラグインによる拡張はできません。\nおわりに ちょっと触っただけでも、これは一気にできることが増えたなあという気持ちにさせられる、大きいアップデートです。\n特に、データソースがプラグインで拡張できること、抽出やクレンジングの処理を完全にコントロールできること、が大きな価値で、技術的には プラグインさえあれば（もしくは自分で作れば）、だいたいどこからでも何でも取ってこられる し、さらに どのようにでも加工して取り込める ようになったとも言えます。\n一方で、以下のような点は今後の改善余地というか、運用上の注意を要する点というか、そういうのもありそうに思いました。\nファイルの更新への追従 例えば、あるデータソース上のファイルやデータが、ナレッジに登録されたあとに更新されたとしても、Dify 側ではそれを検知できません ファイルの更新をナレッジに反映するには、人間が再度パイプラインをトリガする必要があります アプリにスケジュール機能が実装されたら、アプリからナレッジの API を定期的に叩いて同期させるようなこともできるかもしれませんが…… ファイルの権限 データソース側でデータやファイルのアクセス権を細かく設定していたとしても、それは Dify 側とは（当然ながら）連動せず、Dify に取り込まれた時点で Dify の制御下に置かれます ワークスペースの分離やナレッジのパーミッションに注意しないと、意図せずデータやファイルを参照されてしまう可能性もありそうです Enterprise 版で SSO を使っていて、かつデータソース側でも同じ ID が使われているのであれば、どうにかなってくれる…… とうれしいのですが、難易度は高そうな印象です 何はともあれ、夢のある機能でおもしろいですね。いろいろ触ってみましょう。触っていた段階ではバグも多かったのですが、直せそうな範囲でちょこちょこと修正のお手伝いもしていましたし、リリース時点（またはリリース後の早い段階）でもっと安定した状態になることに期待しています。\n","date":"2025-09-04T15:00:00Z","image":"/archives/5422/img/image-417-1.jpg","permalink":"/archives/5422/","title":"RAG 2.0： Dify の新しいナレッジパイプラインを探る"},{"content":"はじめに Dify には有償の Enterprise 版があります。動かすにはライセンスが必要ですし、OSS ではないのでソースコードも見られないのですが、一方で、Enterprise 版のドキュメントや、デプロイに必要なアセット一式は、インタネットで公開されています。\nで、Enterprise 版で動く Dify のバージョンが 0.x だった頃に、そういう公開されているいろいろを探索してフムフムと技術的な好奇心を満たしていたのですが、最近、Enterprise 版の Dify が 1.x に引き上げられ、プラグインに対応しました。そんなわけで、改めてちょっとだけ中身を探ったので、簡単に紹介です。Community 版へのコントリビューションを続けるなかで、とくに Enterprise 版でのプラグイン周辺のアーキテクチャが気になっていたのでした。\nドキュメントでも細かくは説明されていない部分なので、Enterprise 版の導入や、導入済みの Enterprise 版のアップグレード（プラグインアーキテクチャへのマイグレーション）を検討している場合は、参考になる…… かも…… しれません。\nおことわり Enterprise 版の利用にはライセンスが必要です。前述のとおり必要なアセットはインタネットで公開されているので、デプロイ作業それ自体は実施できます。が、ライセンスがないとアクティベーションできない ので、デプロイできても実際には使えません。\nで、ぼくはコントリビューションを 業務ではなくプライベートの趣味 でやっている 個人 なので、当然、Enterprise 版のライセンスは 持っていません。したがって、本エントリで扱う内容は、Enterprise 版の実環境を触って調べたもの ではなく、インタネットで 公開されているリソースのみ を元に確認できた範囲のものです。Enterprise 版に固有のコンポーネントの大部分はソースコードが公開されていないので、実際に動かせもしないしソースコードからの推測もできないし、な部分がたくさんあり、細かいところはだいぶふわっとしています。\nそのため、実装の細かい解説というよりは、全体像と処理の流れをふんわりつかめるくらいの荒い粒度で書いています。また、Kubernetes 環境にデプロイした場合の構成しか確認していないので、AWS や Docker Compose など別のプラットフォームに構成した場合には、本エントリの内容は必ずしも適合しません。さらに、Community 版の構成は充分に知っている前提で書いています。\nなお、本エントリ公開日時点で最新のリリースを基にしています。\n全体的な構成 昔： Dify 0.x 時代の構成（プラグイン機能なし） 昔、Enterprise 版に含まれる Dify が 0.x だった頃の、Kubernetes 上に構成した場合の全体像はこんな感じでした。\n---\rconfig:\rtheme: 'base'\rthemeVariables:\rprimaryColor: '#0033ff'\rprimaryTextColor: '#fff'\rsecondaryColor: '#888'\rlineColor: '#333'\rtertiaryColor: '#e5eaff'\rtertiaryBorderColor: '#888'\rfontSize: '24px'\r---\rflowchart LR\rA(Administrators)\rU(Users)\rEW(Enterprise Web)\rEA(Enterprise API)\rCW(Web)\rCA(API\nwith enterprise features)\rCO(Worker)\rCDB(DB)\rA --\u003e EW\rU --\u003e CW\rEA \u003c--\u003e CA\rsubgraph Dify Enterprise\rEW --\u003e EA\rsubgraph Dify Community\rCW --\u003e CA\rCA \u003c--\u003e CO\rCA --\u003e CDB\rCO --\u003e CDB\rCA --\u003e CV(Vector DB)\rCA --\u003e CS(Sandbox)\rCS --\u003e CSSRF(SSRF Proxy)\rend\rendCommunity 版と同じ一式がそのまま動いていて、横に Enterprise 版に固有の管理機能を提供するコンテナ（とそれ用のフロントエンドを提供するコンテナ）が追加で生えてくる感じです。Community 版の API コンテナは Enterprise 版で使える追加機能が有効化されていて、Enterprise 用の API コンテナと連携して動作します。\n今： Dify 1.x 時代の構成（プラグイン機能あり） プラグインに対応した最近のバージョンでは、Enterprise 版は Kubernetes 版ではこんな感じになっています。これは初期状態で、後述しますがプラグインをインストールするとプラグインごとに新しい Pod が増えていきます。また、環境の外に何らかのコンテナレジストリが別途必要です。\n---\rconfig:\rtheme: 'base'\rthemeVariables:\rprimaryColor: '#0033ff'\rprimaryTextColor: '#fff'\rsecondaryColor: '#888'\rlineColor: '#333'\rtertiaryColor: '#e5eaff'\rtertiaryBorderColor: '#888'\rfontSize: '24px'\r---\rflowchart LR\rA(Administrators)\rU(Users)\rCR((Container\nRegistry))\rEW(Enterprise Web)\rEA(Enterprise API)\rEAudit(Enterprise Audit)\rECRD(CRD Controller)\rEM(MinIO)\rCW(Web)\rCA(API\nwith enterprise features)\rCP(Plugin Daemon)\rCO(Worker)\rCDB(DB)\rA --\u003e EG --\u003e EW\rU --\u003e EG --\u003e CW\rEA \u003c--\u003e CA\rsubgraph Dify Enterprise\rEG(Gateway)\rECP(Plugin Connector)\rECR((Custom\nResource))\rEW --\u003e EA\rEG --\u003e EAudit\rEA --\u003e EAudit\rCP --\u003e ECP\rECP --\u003e ECR\rECP --\u003e EM\rECRD --\u003e ECR\rEAudit --\u003e CDB\rsubgraph Dify Community\rCW --\u003e CA\rCA \u003c--\u003e CO\rCA --\u003e CV(Vector DB)\rCA --\u003e CS(Sandbox)\rCS --\u003e CSSRF(SSRF Proxy)\rCA --\u003e CP\rCA --\u003e CDB\rCO --\u003e CDB\rCP --\u003e CDB\rend\rend構成要素をふんわりと仕分けるとこんな感じです。\nDify 一式のコンテナ群 Community 版とほぼ同じ構成で動作する API コンテナでは Enterprise 版で使える追加機能が有効化されている Plugin Daemon は、Community 版の Local Runtime とは異なり、Serverless Runtime として動作している（後述） 管理機能用のコンテナ群 Enterprise 版に固有の管理機能を提供する Web UI（フロントエンド）と API（バックエンド）のコンテナで構成される 監査ログ用のコンテナ群 Enterprise 版の監査ログ機能を提供する ゲートウェイとログ管理機能のコンテナで構成される プラグイン管理用のコンテナ群 Enterprise 版のプラグインのインストールや実行を担う ちょっと複雑なので後述 管理機能まわり Enterprise 版に固有のダッシュボードやら管理機能やらを提供する部分です。ここは Dify 0.x 時代の構成と変わっていません。説明は省略。\n---\rconfig:\rtheme: 'base'\rthemeVariables:\rprimaryColor: '#0033ff'\rprimaryTextColor: '#fff'\rsecondaryColor: '#888'\rlineColor: '#333'\rtertiaryColor: '#e5eaff'\rtertiaryBorderColor: '#888'\rfontSize: '24px'\r---\rflowchart TB\rEW(Enterprise Web)\rEA(Enterprise API)\rCW(Web)\rCA(API\nwith enterprise features)\rCP(Plugin Daemon)\rCO(Worker)\rCDB(DB)\rEA \u003c--\u003e CA\rsubgraph Dify Enterprise\rEW --\u003e EA\rsubgraph Dify Community\rCW --\u003e CA\rCA \u003c--\u003e CO\rCA --\u003e CV(Vector DB)\rCA --\u003e CS(Sandbox)\rCS --\u003e CSSRF(SSRF Proxy)\rCA --\u003e CP\rCA --\u003e CDB\rCO --\u003e CDB\rCP --\u003e CDB\rend\rend監査ログまわり Enterprise 版の監査ログ機能を提供する部分です。\n管理者やユーザの操作は技術的には Web UI や API への HTTP リクエストなので、全部とにかくゲートウェイ的な役割のコンテナを経由させればまとめてログが取れるよね、な発想の実装に見えます。\n---\rconfig:\rtheme: 'base'\rthemeVariables:\rprimaryColor: '#0033ff'\rprimaryTextColor: '#fff'\rsecondaryColor: '#888'\rlineColor: '#333'\rtertiaryColor: '#e5eaff'\rtertiaryBorderColor: '#888'\rfontSize: '24px'\r---\rflowchart LR\rA(Administrators)\rU(Users)\rEW(Enterprise Web)\rEA(Enterprise API)\rEAudit(Enterprise Audit)\rCW(Web)\rCA(API\nwith enterprise features)\rCP(Plugin Daemon)\rCO(Worker)\rCDB(DB)\rA --\u003e EG --\u003e EW\rU --\u003e EG --\u003e CW\rEA \u003c--\u003e CA\rsubgraph Dify Enterprise\rEG(Gateway)\rEW --\u003e EA\rEG --\u003e EAudit\rEA --\u003e EAudit\rEAudit --\u003e CDB\rsubgraph Dify Community\rCW --\u003e CA\rCA \u003c--\u003e CO\rCA --\u003e CV(Vector DB)\rCA --\u003e CS(Sandbox)\rCS --\u003e CSSRF(SSRF Proxy)\rCA --\u003e CP\rCA --\u003e CDB\rCO --\u003e CDB\rCP --\u003e CDB\rend\rendこのあたりのソースコードはまるっと非公開なので詳しいことはわかりませんが、Kubernetes 的には Enterprise 版で用意される Ingress のバックエンドはすべてこの Gateway コンテナであり、このコンテナの中で動作する Caddy がログ情報の Redis への記録と HTTP リクエストの本来の宛先への転送を担っているように見えました。Caddy の中では専用に開発されたと思われるログ取得用ミドルウェアが構成されています。\nさらに、Redis に置かれたログ情報は、Audit コンテナが吸い上げてデータベースに永続化していそうな気配でした。この Audit コンテナは、監査ログを管理する API も提供していそうです。\nプラグイン管理まわり プラグインをインストールしたり、インストール済みのプラグインを実行したりする部分です。個人的にいちばん気になっていたところです。\nおおまかな構成と動き 前提として、Community 版では、プラグインに関連する実装は Plugin Daemon ひとつだけで、インストールも実行もすべてが単一のコンテナの中で完結しています。実装上は、これは Plugin Daemon の Local Runtime と呼ばれる動作モードです。\n一方で、Enterprise 版で動作する Plugin Daemon は、最近ちょこっとだけ情報が公開された Serverless Runtime と呼ばれる動作モードで構成されます。このモードの動きを概要レベルでまとめるとこんな感じです。\nプラグインをインストールするとき 専用のベースイメージ上にプラグインが展開され、新しいコンテナイメージ としてビルドされます ビルドされたイメージは コンテナレジストリにプッシュ されます コンテナイメージが 新しい Pod として起動され、以降、常駐します（AWS 上で動いている場合は AWS Lambda として準備されます） プラグインを実行するとき Plugin Daemon から、インストール時に起動された Pod に実行指示が送られます（AWS 上で動いている場合は AWS Lambda が叩かれます） もうちょっとくわしく これだけだとまだ漠然としているので、ここから、これをさらにもう一段階踏み込んで読み解きます。理解の前提となるポイントは以下の通りです。\nEnterprise 版をデプロイするときに、**==DifyPlugin という名前の Custom Resource Definition（CRD）が ==Kubernetes クラスタに **追加 されます Kubernetes の文脈で、DifyPlugin という CR に対応する Custom Controller として、CRD Controller コンテナ が動作します 何らかのコンテナレジストリ が別途必要です この前提で、プラグインをインストールするとき は、全体としては以下の流れで動作します。数字は図中のそれと対応しています。\n---\rconfig:\rtheme: 'base'\rthemeVariables:\rprimaryColor: '#0033ff'\rprimaryTextColor: '#fff'\rsecondaryColor: '#888'\rlineColor: '#333'\rtertiaryColor: '#e5eaff'\rtertiaryBorderColor: '#888'\rfontSize: '24px'\r---\rflowchart\rECRD(CRD Controller)\rEM(MinIO)\rCA(API\nwith enterprise features)\rCP(Plugin Daemon)\rCDB(DB)\rEA \u003c.-\u003e CA\rsubgraph Dify Enterprise\rEA(Enterprise API)\rECP(Plugin Connector)\rECR((Custom\nResource))\rEPP(Plugin Runner)\rEBUILD(Kaniko Executor)\rECREG((Container\nRegistry))\rCP -- [ 2 ] --\u003e ECP\rECP -- [ 8 ] --\u003e CP\rECP -- [ 3 ] --\u003e ECR\rECP -- [ 3 ] --\u003e EM\rECRD -- [ 4 ] --\u003e ECR\rECRD -- [ 7 ] --\u003e EPP\rECRD -- [ 5 ] --\u003e EBUILD -- [ 6 ] --\u003e ECREG -- [ 7 ] --\u003e EPP\rEBUILD -- [ 5 ] --\u003e EM\rCP .-\u003e EPP\rEPP .-\u003e CP\rsubgraph Dify Community\rCA -- [ 1 ] --\u003e CP\rCP -- [ 9 ] --\u003e CA\rCA .-\u003e CDB\rCP .-\u003e CDB\rend\rend プラグインをインストールするとき API コンテナから Plugin Daemon に、プラグインのインストールが指示されます Plugin Daemon から Plugin Connector に、プラグインのインストールが指示されます Plugin Connector は、プラグインごとにインストールに必要なアセット一式を準備して MinIO に格納し、DifyPlugin（CR）を Kubernetes クラスタに作成します CRD Controller は、Dify Plugin（CR）が作成されたことを認識します CRD Controller は、CR の構成に基づき、コンテナイメージを Kaniko でビルドします（Executor の Pod が起動され、コンテキストは MinIO から取得されます） ビルドされたイメージがコンテナレジストリにプッシュされます CRD Controller は、CR の構成に基づき、前項のステップでビルドされたコンテナイメージを使う Pod（Plugin Runner）と、実行時のエンドポイントとなる Service を作成します Plugin Runner の起動完了後、Plugin Connector は Pod（Plugin Runner）のエンドポイント情報を Plugin Daemon に返します API コンテナに処理の完了が伝達され、インストールが完了します こうしてインストールされたプラグインは、実行するとき は次の流れで動作します。いちどインストールされてしまえば、実行時には Plugin Connector や CRD Controller は（たぶん）介さないので、とてもシンプルです。\n---\rconfig:\rtheme: 'base'\rthemeVariables:\rprimaryColor: '#0033ff'\rprimaryTextColor: '#fff'\rsecondaryColor: '#888'\rlineColor: '#333'\rtertiaryColor: '#e5eaff'\rtertiaryBorderColor: '#888'\rfontSize: '24px'\r---\rflowchart\rECRD(CRD Controller)\rEM(MinIO)\rCA(API\nwith enterprise features)\rCP(Plugin Daemon)\rCDB(DB)\rEA \u003c.-\u003e CA\rsubgraph Dify Enterprise\rEA(Enterprise API)\rECP(Plugin Connector)\rECR((Custom\nResource))\rEPP(Plugin Runner)\rEBUILD(Kaniko Executor)\rECREG((Container\nRegistry))\rCP .-\u003e ECP\rECP .-\u003e CP\rECP .-\u003e ECR\rECP .-\u003e EM\rECRD .-\u003e ECR\rECRD .-\u003e EPP\rECRD .-\u003e EBUILD .-\u003e ECREG .-\u003e EPP\rEBUILD .-\u003e EM\rCP -- [ 2 ] --\u003e EPP\rEPP -- [ 3 ] --\u003e CP\rsubgraph Dify Community\rCA -- [ 1 ] --\u003e CP\rCP -- [ 4 ] --\u003e CA\rCA .-\u003e CDB\rCP .-\u003e CDB\rend\rend プラグインを実行するとき API コンテナから Plugin Daemon に、プラグインの実行が指示されます 前項のステップで起動状態になった Pod（Plugin Runner）のエンドポイントに、Plugin Daemon からリクエストが投げられます Pod（Plugin Runner）から結果が Plugin Daemon に返ります Plugin Daemon から結果が API コンテナに返ります とくにインストール時の処理の流れは複雑ですが、俯瞰してみると、つまり、すべてのプラグインをプラグインごとに完全に独立した Pod（AWS では AWS Lambda）で動作させるアーキテクチャであることがわかります。\nプラグインの実処理にかかる負荷を Plugin Daemon からは完全に切り離せているほか、プラグイン同士の影響も完全に排除できていることになり、プラグインが大量にインストールされた環境でも、Kubernetes クラスタ側の計算資源が潤沢であれば、全体としてはうまく負荷が分散されて安定しそうです。やや富豪的な印象もありますが、可用性や拡張性などの面でもメリットがありそうですね。\nイメージのビルドや Pod の作成が、Plugin Connector ではなくあくまで Custom Resource とそのための Custom Controller を介して行われるあたりもおもしろい構成です。責任範囲を限定して、非同期的に処理でき、超大規模なデプロイにも耐えられるような工夫でしょうか。\nPlugin Connector も CRD Controller も、ソースコードが非公開なのが惜しいですね……。中を見たい……。あと、Kaniko、もうメンテナンスされなくなっているようですが、いつまで使うのかなあというのも気になるポイントです。\nおわりに 公開情報をもとに、Dify の Enterprise 版のアーキテクチャをちょっとだけ（かつ推測交じりで）紹介しました。\nプラグインまわりの動きについては、インストールのたびにコンテナイメージが新しくできてプッシュされるっぽいなあというのは推測できていたものの、Kubernetes 上に Custom Resource と Custom Controller ができあがるのは予想外でした。よくできていますね。\n一方で、それを実現するために、CRD をクラスタに突っ込む権限がないと動作要件を満たせなくなる点だったり、コンテナレジストリが必要だったり、プラグインの数だけ Pod が増えていったりと、権限、環境、リソースなどの面で、本番環境に導入するにはちょこちょこと要注意なポイントがあるのかもしれません。\nなにはともあれ、こういう探求には自由研究的なおもしろさがあってよいものですね。引き続き、興味と好奇心の続く限り、今まで通り趣味の範囲でコントリビューションを続けていきます。\n","date":"2025-07-21T15:26:49Z","image":"/archives/5375/img/image-408.png","permalink":"/archives/5375/","title":"Dify の Enterprise 版のアーキテクチャをちょっとだけ探る"},{"content":"はじめに Dify がすこぶる便利でおもしろいので、もりもりと使ったりちまちまとコントリビューションをしたりしています。\n今回、Dify の 運用管理系 の操作、たとえば、\n✨ アプリのインポートやエクスポート ✨ ナレッジの作成とファイルの追加 ✨ ワークスペースのメンバの改廃 ✨ モデルの追加やシステムモデルの設定 などを、PowerShell のコマンドレットで行える ようにするモジュール PSDify をリリースしたので、かんたんに使い方などを紹介します。\nPSDify: A PowerShell module for workspace management for Dify これは Dify Advent Calendar の 1 日目 です。\n利用イメージ たとえば、以下のシナリオを全自動で行えます。\n既存の Dify 環境にログイン（クラウド版でもコミュニティ版でも）する または新しくデプロイしたコミュニティ版の環境に 管理者アカウントを作成 する ワークスペースに新しく モデルを追加 して、システムモデルを変更 する 新しい ナレッジを作成してファイルを追加 する 既存の DSL ファイルを 新しいナレッジを使うように修正 したうえで インポート する インポートした アプリで API キーを発行 する API キーを使って アプリにチャットメッセージを送信 する ワークスペースの 全アプリをエクスポート する ワークスペースに新しい メンバを招待 する これは、具体的なコマンドでは以下な感じです。\n# 既存の環境にログインする PS\u0026gt; Connect-Dify -AuthMethod \u0026#34;Code\u0026#34; -Email \u0026#34;info@example.com\u0026#34; # クラウド版 PS\u0026gt; Connect-Dify -Server \u0026#34;https://dify.example.com\u0026#34; -Email \u0026#34;info@example.com\u0026#34; # コミュニティ版 # 新しい環境に管理者アカウントを作成する（コミュニティ版のみ） PS\u0026gt; Initialize-Dify -Server \u0026#34;https://dify.example.com\u0026#34; -Email \u0026#34;info@example.com\u0026#34; -Name \u0026#34;Dify\u0026#34; # ワークスペースにモデルを追加する PS\u0026gt; New-DifyModel -Provider \u0026#34;openai\u0026#34; -From \u0026#34;predefined\u0026#34; ` -Credential @{ \u0026#34;openai_api_key\u0026#34; = \u0026#34;sk-proj-****************\u0026#34; } # ワークスペースのシステムモデルを変更する PS\u0026gt; Set-DifySystemModel -Type \u0026#34;llm\u0026#34; -Provider \u0026#34;openai\u0026#34; -Name \u0026#34;gpt-4o-mini\u0026#34; PS\u0026gt; Set-DifySystemModel -Type \u0026#34;text-embedding\u0026#34; -Provider \u0026#34;openai\u0026#34; -Name \u0026#34;text-embedding-3-small\u0026#34; # 新しいナレッジを作成する PS\u0026gt; $Knowledge = New-DifyKnowledge -Name \u0026#34;My New Knowledge\u0026#34; # ナレッジにファイルを追加して、インデキシングの完了を待つ PS\u0026gt; Get-Item -Path \u0026#34;Docs/*.md\u0026#34; | Add-DifyDocument -Knowledge $Knowledge -Wait # 既存の DSL ファイル内のナレッジ ID を新しい ID に置き換えて別ファイルとして保存する PS\u0026gt; $RawContent = Get-DifyDSLContent -Path \u0026#34;oldapp.yml\u0026#34; PS\u0026gt; $RawContent = $RawContent -replace \u0026#34;dc642635-9416-4794-a99d-0d02bc4ead5e\u0026#34;, $Knowledge.Id PS\u0026gt; Set-DifyDSLContent -Path $UpdatedDSLFile -Content \u0026#34;newapp.yml\u0026#34; # 新しくできた DSL ファイルをインポートする PS\u0026gt; $App = Import-DifyApp -Path $UpdatedDSLFile # インポートしたアプリで API キーを発行する PS\u0026gt; $APIKey = New-DifyAppAPIKey -App $App # インポートしたアプリにチャットメッセージを送信する PS\u0026gt; $env:PSDIFY_APP_TOKEN = $Key.Token PS\u0026gt; $env:PSDIFY_APP_URL = \u0026#34;https://dify.example.com\u0026#34; PS\u0026gt; Send-DifyChatMessage -Message \u0026#34;おはよ！\u0026#34; -NewSession # 全アプリを DSL ファイルとしてエクスポートする PS\u0026gt; Get-DifyApp | Export-DifyApp # ワークスペースに新しいメンバを招待する PS\u0026gt; New-DifyMember -Email \u0026#34;dify-editor1@example.com\u0026#34; -Role \u0026#34;editor\u0026#34; PS\u0026gt; New-DifyMember -Email \u0026#34;dify-normal1@example.com\u0026#34; -Role \u0026#34;normal\u0026#34; 画面出力イメージはこんな感じです。\nなお、返すオブジェクトは PSCustomObject そのままなので、（人間目線で）見やすくしたい場合は適宜 Select-Object や Format-Table を併用してください。types.ps1xml や format.ps1xml を書く気力はあんまりないです（過去に書いたことがありますが、書いたあとも維持が大変なので、Dify 側の仕様変更に追従する苦労を考えるとあまりやりたくないです）。\n利用上の注意 当然ですが、非公式 のものです。利用する場合は 自己責任 でお願いします。不具合が起きても Dify や LangGenius に文句は言えません。 Dify の API を使っていますが、公式にはサポートされていない使い方 をしています。Dify の API の仕様が少しでも変わると動かなくなる可能性 があります。 もともとぼく個人の作業の助けとなるようにつくったものです。とにかく動く ことを目指しているので、エラーハンドリングやヘルプの実装は不十分ですし、今後もたぶんあまり方針は変わりません。 バグ報告や機能追加要望は GitHub の Issue へお願いします。 インストール PowerShell Gallery で公開 しているので、Install-Module でインストールできます。\nInstall-Module -Name PSDify コマンドレット一覧 主なものを抜粋して一覧しています。\nより詳しい説明は GitHub においてあるドキュメント（日本語） を参照してください。\nカテゴリ コマンドレット 説明 認証 Connect-Dify Disconnect-Dify Dify へのログイン・Dify からのログアウトを行います。 コミュニティ版向けにパスワード認証、クラウド環境向けにメール認証に対応しています。 アプリ Get-DifyApp Remove-DifyApp アプリを取得・削除します。 アプリ Import-DifyApp Export-DifyApp アプリをローカルの DSL ファイルからインポート、または DSL ファイルへエクスポートします。 アプリ Get-DifyDSLContent Set-DifyDSLContent DSL ファイルの中身を読んだり書いたりします。 アプリ New-DifyAppAPIKey Get-DifyAppAPIKey Remove-DifyAppAPIKey アプリの API キーを作成・取得・削除します。 ナレッジ New-DifyKnowledge Get-DifyKnowledge ナレッジを作成・取得します。 ナレッジ Add-DifyDocument Get-DifyDocument ナレッジのドキュメントを追加・取得します。 メンバ New-DifyMember Get-DifyMember Remove-DifyMember ワークスペースのメンバを追加（招待）・取得・削除します。 メンバ Set-DifyMemberRole ワークスペースのメンバのロールを変更します。 モデル New-DifyModel Get-DifyModel ワークスペースのモデルを追加・取得します。 モデル Get-DifySystemModel Set-DifySystemModel ワークスペースのシステムモデルを取得・設定します。 タグ Get-DifyTag Get-DifyAppTag Get-DifyKnowledgeTag タグの情報を取得します。 情報取得 Get-DifyVersion Dify のバージョン情報を取得します。 情報取得 Get-DifyProfile 認証したアカウントの情報を取得します。 初期設定 Initialize-Dify 管理者アカウントを作成します（コミュニティ版のみ）。 その他 Set-PSDifyConfiguration HTTPS 接続時の証明書の検証を無効化できます。 チャット操作 Send-DifyChatMessage アプリにチャットメッセージを送信します。 使い方 認証 ほかのコマンドを実行する前に、まずは Dify に対して認証を要求します。\nクラウド版 では通常は SSO で認証して利用しますが、SSO で認証したアカウントでも、紐づけられたメールアドレスでメール認証が行えます。以下のコマンドを実行するとメールで認証コードが届くので、それをプロンプトに投げると認証が完了します。\n# メールによる認証（実行後、メールで届いたコードを手動で入力する） PS\u0026gt; Connect-Dify -AuthMethod \u0026#34;Code\u0026#34; -Email \u0026#34;dify@example.com\u0026#34; Enter the code sent to dify@example.com: 123456 Server Version Name Email ------ ------- ---- ----- https://cloud.dify.ai 0.12.1 kurokobo dify@example.com コミュニティ版 では、通常はパスワード認証です。-Server と -Email で URL とメールアドレスを指定して実行します。\nPS\u0026gt; Connect-Dify -Server \u0026#34;https://dify.example.com\u0026#34; -Email \u0026#34;dify@example.com\u0026#34; Enter password for dify@example.com: **************** Server Version Name Email ------ ------- ---- ----- https://dify.example.com 0.12.1 kurokobo dify@example.com なお、自己署名証明書で HTTPS 化している場合など、構成によっては証明書の検証がコケて認証できないことがあります。リスクを受け入れて雑に回避したい場合は、Connect-Dify の前に次のコマンドを実行すると証明書の検証を無効できます。\nPS\u0026gt; Set-PSDifyConfiguration -IgnoreSSLVerification $true 認証ができれば、あとは自由に操作できます。\nアプリの管理 アプリの情報を確認（Get-DifyApp）したり、\n# すべてのアプリを取得 PS\u0026gt; Get-DifyApp # ID や名前、タイプ、タグでフィルタ PS\u0026gt; Get-DifyApp -Id \u0026#34;2a5f3b01-a14c-4f57-b5fd-9b7aa69c250e\u0026#34; PS\u0026gt; Get-DifyApp -Name \u0026#34;My App\u0026#34; PS\u0026gt; Get-DifyApp -Mode \u0026#34;chat\u0026#34; # \u0026#34;chat\u0026#34;, \u0026#34;workflow\u0026#34;, \u0026#34;agent-chat\u0026#34;, \u0026#34;channel\u0026#34;, \u0026#34;all\u0026#34; PS\u0026gt; Get-DifyApp -Tags \u0026#34;Tag A\u0026#34;, \u0026#34;Tag B\u0026#34; アプリをエクスポート（Export-DifyApp）したり、\n# アプリのエクスポート（\u0026#34;DSLs\u0026#34; フォルダに保存） PS\u0026gt; Get-DifyApp | Export-DifyApp # 保存先のディレクトリを変更 PS\u0026gt; Get-DifyApp | Export-DifyApp -Path \u0026#34;./path/to/your/directory\u0026#34; # シークレットを含める場合 PS\u0026gt; Get-DifyApp | Export-DifyApp -IncludeSecret アプリをインポート（Import-DifyApp）したり、\n# アプリのインポート PS\u0026gt; Import-DifyApp -Path \u0026#34;DSLs/*.yml\u0026#34; アプリを削除（Remove-DifyApp）したりできます。\n# アプリの削除 PS\u0026gt; Get-DifyApp -Name \u0026#34;Unused App\u0026#34; | Remove-DifyApp API キーも発行したり確認したりできます。\n# API キーの発行 PS\u0026gt; Get-DifyApp -Name \u0026#34;My Chat App\u0026#34; | New-DifyAppAPIKey # API キーの確認 PS\u0026gt; Get-DifyApp -Name \u0026#34;My Chat App\u0026#34; | Get-DifyAppAPIKey ナレッジの管理 ナレッジの情報を確認（Get-DifyKnowledge）したり、\n# すべてのナレッジを取得 PS\u0026gt; Get-DifyKnowledge # ID や名前、タグでフィルタ PS\u0026gt; Get-DifyKnowledge -Id \u0026#34;a48750ec-c36d-4b0f-aa22-2771c466ba3a\u0026#34; PS\u0026gt; Get-DifyKnowledge -Name \u0026#34;My Knowledge\u0026#34; PS\u0026gt; Get-DifyKnowledge -Tags \u0026#34;Tag A\u0026#34;, \u0026#34;Tag B\u0026#34; 空のナレッジを作成（New-DifyKnowledge）したり、\n# 空のナレッジを作成 PS\u0026gt; New-DifyKnowledge -Name \u0026#34;My New Knowledge\u0026#34; # ナレッジの作成（説明文を追加） PS\u0026gt; New-DifyKnowledge -Name \u0026#34;My New Knowledge\u0026#34; -Description \u0026#34;This is a new knowledge.\u0026#34; ナレッジを削除（Remove-DifyKnowledge）したりできます。\n# ナレッジの削除 Get-DifyKnowledge -Name \u0026#34;Unused Knowledge\u0026#34; | Remove-DifyKnowledge ナレッジ内のドキュメントを確認（Get-DifyDocument）したり、\n# ナレッジのすべてのドキュメントの取得 PS\u0026gt; Get-DifyKnowledge -Name \u0026#34;My Knowledge\u0026#34; | Get-DifyDocument # ID や名前でフィルタ Get-DifyKnowledge -Name \u0026#34;My Knowledge\u0026#34; | Get-DifyDocument -Id \u0026#34;...\u0026#34; Get-DifyKnowledge -Name \u0026#34;My Knowledge\u0026#34; | Get-DifyDocument -Name \u0026#34;...\u0026#34; ナレッジにファイルをアップロード（Add-DifyDocument）したりもできます。デフォルトで、Web の設定画面でいう『自動』『高品質（システムモデル）』『ベクトル検索』で構成されます。あまり細かいカスタマイズはまだ実装していません。\n# 前提： アップロードするナレッジを取得 $Knowledge = Get-DifyKnowledge -Name \u0026#34;My New Knowledge\u0026#34; # ドキュメントのアップロード Get-Item -Path \u0026#34;Docs/*.md\u0026#34; | Add-DifyDocument -Knowledge $Knowledge # システムモデル以外を使う $EmbeddingModel = Get-DifyModel -Provider \u0026#34;openai\u0026#34; -Name \u0026#34;text-embedding-3-small\u0026#34; Add-DifyDocument -Knowledge $Knowledge -Path \u0026#34;Docs/*.md\u0026#34; -IndexMode \u0026#34;high_quality\u0026#34; -Model $EmbeddingModel # エコノミーモードにする Add-DifyDocument -Knowledge $Knowledge -Path \u0026#34;Docs/*.md\u0026#34; -IndexMode \u0026#34;economy\u0026#34; Add-DifyDocument は、インデキシングの完了を待機する -Wait オプションも使えます。\nメンバの管理 ワークスペースのメンバの情報を確認（Get-DifyMember）したり、\n# すべてのメンバの取得 PS\u0026gt; Get-DifyMember # ID や名前、メールアドレスでフィルタ PS\u0026gt; Get-DifyMember -Id \u0026#34;6cee2985-f7f3-4b96-aa34-15a0b80a1a2c\u0026#34; PS\u0026gt; Get-DifyMember -Name \u0026#34;kurokobo\u0026#34; PS\u0026gt; Get-DifyMember -Email \u0026#34;dify@example.com\u0026#34; 新しいメンバをワークスペースに招待（New-DifyMember）したり、\n# メンバの招待 PS\u0026gt; New-DifyMember -Email \u0026#34;dify-editor1@example.com\u0026#34; -Role \u0026#34;normal\u0026#34; # \u0026#34;admin\u0026#34;, \u0026#34;editor\u0026#34;, \u0026#34;normal\u0026#34; メンバのロールを変更（Set-DifyMemberRole）したり、\n# メンバのロールの変更 PS\u0026gt; Get-DifyMember -Email \u0026#34;dify-editor1@example.com\u0026#34; | Set-DifyMemberRole -Role \u0026#34;editor\u0026#34; メンバを削除（Remove-DifyMember）したりできます。\n# メンバの削除 PS\u0026gt; Get-DifyMember -Email \u0026#34;dify-editor1@example.com\u0026#34; | Remove-DifyMember モデルの管理 ワークスペースに追加されているモデルを確認（Get-DifyModel）したり、\n# すべてのモデルの取得 PS\u0026gt; Get-DifyModel # プロバイダや種類、名前、タイプでフィルタ PS\u0026gt; Get-DifyModel -Provider \u0026#34;openai\u0026#34; PS\u0026gt; Get-DifyModel -From \u0026#34;predefined\u0026#34; # \u0026#34;predefined\u0026#34;, \u0026#34;customizable\u0026#34; PS\u0026gt; Get-DifyModel -Name \u0026#34;gpt-4o-mini\u0026#34; PS\u0026gt; Get-DifyModel -Type \u0026#34;llm\u0026#34; # \u0026#34;llm\u0026#34;, \u0026#34;text-embedding\u0026#34;, \u0026#34;speech2text\u0026#34;, \u0026#34;moderation\u0026#34;, \u0026#34;tts\u0026#34;, \u0026#34;rerank\u0026#34; 新しいモデルを追加（New-DifyModel）したりできます。-Provider や -Credential で渡すべき情報はプロバイダやモデルによって異なるため、ブラウザの開発者ツールでいちど手作業で追加して、実際の HTTP リクエストを確認するのが確実です。\n# モデルの追加（事前定義モデルの追加、OpenAI の例） PS\u0026gt; New-DifyModel -Provider \u0026#34;openai\u0026#34; -From \u0026#34;predefined\u0026#34; ` -Credential @{ \u0026#34;openai_api_key\u0026#34; = \u0026#34;sk-proj-****************\u0026#34; } # モデルの追加（カスタムモデルの追加、OpenAI の例） PS\u0026gt; New-DifyModel -Provider \u0026#34;openai\u0026#34; -From \u0026#34;customizable\u0026#34; ` -Type \u0026#34;llm\u0026#34; -Name \u0026#34;gpt-4o-mini\u0026#34; ` -Credential @{ \u0026#34;openai_api_key\u0026#34; = \u0026#34;sk-proj-****************\u0026#34; } システムモデルも確認（Get-DifySystemModel）したり変更（Get-DifySystemModel）したりできます。\n# すべてのシステムモデルの取得 PS\u0026gt; Get-DifySystemModel # タイプでフィルタ PS\u0026gt; Get-DifySystemModel -Type \u0026#34;llm\u0026#34; # \u0026#34;llm\u0026#34;, \u0026#34;text-embedding\u0026#34;, \u0026#34;rerank\u0026#34;, \u0026#34;speech2text\u0026#34;, \u0026#34;tts\u0026#34; # システムモデルの変更 PS\u0026gt; Set-DifySystemModel -Type \u0026#34;llm\u0026#34; -Provider \u0026#34;openai\u0026#34; -Name \u0026#34;gpt-4o-mini\u0026#34; 情報取得 接続先のインスタンスの情報（Get-DifyVersion）や、認証したアカウントの情報（Get-DifyProfile）を確認できます。\n# 接続先の Dify のバージョンの取得 PS\u0026gt; Get-DifyVersion # 認証したアカウントの情報の取得 PS\u0026gt; Get-DifyProfile インスタンスの初期化 コミュニティ版に限定した機能ですが、新しくデプロイした直後の管理者アカウントの作成（Initialize-Dify）もできます。初期化パスワード（.env で INIT_PASSWORD で設定できるやつ、デフォルトはナシ）を指定している場合でも大丈夫です。\n# 管理者アカウントの作成 PS\u0026gt; Initialize-Dify -Server \u0026#34;https://dify.example.com\u0026#34; -Email \u0026#34;dify@example.com\u0026#34; チャットメッセージの送信 ひとによってはこれだけでも便利かもしれませんが、アプリにチャットも送れます。動作には環境変数の設定が必要です。ログが勝手に Logs フォルダに吐かれます。\n# 環境変数の設定 PS\u0026gt; $env:PSDIFY_APP_URL = \u0026#34;https://dify.example.com\u0026#34; PS\u0026gt; $env:PSDIFY_APP_TOKEN = \u0026#34;app-****************\u0026#34; # チャットメッセージの送信 PS\u0026gt; Send-DifyChatMessage -Message \u0026#34;Hello, Dify!\u0026#34; # チャットメッセージの送信（新しいセッションを開始） PS\u0026gt; Send-DifyChatMessage -Message \u0026#34;Hello, Dify!\u0026#34; -NewSession 見た目はこんな感じ。\nモチベーション 日常的にいくつかのインスタンスを運用していると、バックアップ目的でアプリをエクスポートしたり、ワークスペースにメンバを招待したりロールを変更したり、いわゆる運用管理系っぽい操作を頻繁に行うことになります。\nまた、新しいバージョンが出たら採用前に検証が必要ですが、新しく検証用の環境をつくったのなら、そのたびにデプロイ後の管理者アカウントやモデルの追加、ナレッジの作成、アプリの作成をしなければなりません。\nちまちまとコントリビューションもしていることもあって、開発や調査・検証の目的でも環境を作ったり変更したり消したりが必要です。\n最初は Web の GUI から手作業でがんばっていましたが、かなりの頻度と量なので、これはもうどう考えても効率が悪いわけです。この手の作業ならコマンドラインベースの操作でできてしかるべきだし、コマンドでできればスクリプト化も簡単でしょう。\nということでざくざくと作ったのがきっかけです。完全に個人用として作ってきたモノですが、数か月使ってきて実際にとても便利でしたし、そこそこ機能も充実してきたので、今回あらためて整理しなおして公開することにしました。\nおわりに Dify 上での日常の作業をだいぶラクにしてくれる PSDify を紹介しました。自前運用がだいぶラクになるので、興味があればさわってみてください。\nバグ報告や機能追加要望は GitHub の Issue へぜひ。\n","date":"2024-11-30T15:00:00Z","image":"/archives/5331/img/image-407.png","permalink":"/archives/5331/","title":"PSDify： Dify のワークスペースの管理をコマンドで！ アプリ・ナレッジ・モデル・メンバ管理用 PowerShell モジュール"},{"content":"はじめに Ansible のプレイブックの実行結果にめちゃくちゃシンプルなタイムスタンプを表示するだけのコールバックプラグイン community.general.timestamp をリリースしました。\n簡単に使い方を紹介します。\n概要 このプラグインは、プレイブックの実行結果の ヘッダ行（アスタリスクが並んでいる行）の 右端にタイムスタンプを表示 します。デフォルトでは時刻のみです。\n最小限のシンプルなさりげない表示を目指した実装です。かわいいですね。もはやこれだけで充分なことも多いと思っています。\nタイムゾーンの変更と、タイムスタンプのフォーマットの変更にも対応しています。下はタイムゾーンを UTC にして ISO 8601 拡張形式で日付も表示させた例です。\nタイムゾーンやフォーマットの設定に関わらず、タイムスタンプは常にヘッダ行の右端に右寄せで表示 されます（ただし、見出しの文字列が長すぎる場合やウィンドウの幅が狭すぎる場合は、折り返されます）。\n使い方と設定 実行結果にタイムスタンプを表示するには、コレクション community.general の 9.0.0 以降をインストールした状態で、ansible.cfg に以下の設定を追加するだけです。\n[defaults] stdout_callback = community.general.timestamp または、環境変数 DEFAULT_STDOUT_CALLBACK で指定します。\nexport DEFAULT_STDOUT_CALLBACK=\u0026#34;community.general.timestamp\u0026#34; 設定の変更 タイムゾーンやフォーマットを指定したい場合は、ansible.cfg にさらに以下の設定を追加します。設定値は例です。\n[callback_timestamp] timezone = Asia/Tokyo format_string = %Y/%m/%d %H:%M:%S (%Z) それぞれ、次の環境変数でも設定できます。\nexport ANSIBLE_CALLBACK_TIMESTAMP_TIMEZONE=\u0026#34;Asia/Tokyo\u0026#34; export ANSIBLE_CALLBACK_TIMESTAMP_FORMAT_STRING=\u0026#34;%Y/%m/%d %H:%M:%S (%Z)\u0026#34; タイムゾーンの指定 タイムゾーンは、ansible.cfg の callback_timestamp セクションの timezone、または環境変数 ANSIBLE_CALLBACK_TIMESTAMP_TIMEZONE で指定できます。\n指定した値は Python の zoneinfo にそのまま渡されるので、Asia/Tokyo、America/Los_Angeles、UTC など、IANA Time Zone Database で定義された名前が利用できます（英語版 Wikipedia のページ のほうが人間にはやさしいです）。\ntimezone = Asia/Tokyo timezone = America/Los_Angeles timezone = UTC デフォルトは 無指定 なので、デフォルトのタイムスタンプの中身は Naive な（タイムゾーンを持たない）時刻情報です。設定で明示的にタイムゾーンを指定すれば Aware な時刻になります。\nフォーマットの指定 フォーマットは、ansible.cfg の callback_timestamp セクションの format_string、または環境変数 ANSIBLE_CALLBACK_TIMESTAMP_FORMAT_STRING で指定できます。\nフォーマットの値は Python の datetime の strftime() にそのまま渡されるので、strftime() で使えるフォーマット文字列 が使えます。\nformat_string = %Y/%m/%d %H:%M:%S (%Z) format_string = %Y-%m-%dT%H:%M:%S%z format_string = %H:%M:%S.%f デフォルトは %H:%M:%S です。\n応用例 このプラグインは、デフォルトのコールバックプラグインである ansible.builtin.default を拡張する形で実装しています。したがって、ansible.builtin.default で使えるパラメータもそのまま機能します。\n例えば、実行結果を JSON 形式から YAML 形式に切り替える設定とも組み合わせられます。\n[defaults] stdout_callback = community.general.timestamp callback_result_format = yaml [callback_timestamp] timezone = UTC format_string = %Y-%m-%dT%H:%M:%S%z 利用上の注意 残念ながら、AWX や Ansible Runner、Ansible Navigator など EE を使ってプレイブックを実行する場合 には、仕様上、（だいぶ強引にパッチをあてないかぎり）このプラグインは機能しません。。\nEE でコールバックプラグインを変更するややこしさは 別のエントリ で過去に紹介しています。\n参考情報 類似のプラグイン プレイブックの実行結果にタイムスタンプを表示するプラグインとして、既成の ansible.posix.profile_tasks も知られています。ただし、これはタスクごとの実行時間の確認を主目的とするプラグインなので、実行結果に追加される情報が多く、タイムスタンプの確認だけを目的に使うのは少し大げさです。\n$ ansible-playbook main.yml PLAY [timestamp demo] ******************************************************************************************** TASK [timestamp demo] ******************************************************************************************** Tuesday 21 May 2024 23:47:12 +0900 (0:00:00.009) 0:00:00.009 *********** ok: [localhost] =\u0026gt; msg: Hello Ansible !! TASK [timestamp demo] ******************************************************************************************** Tuesday 21 May 2024 23:47:12 +0900 (0:00:00.018) 0:00:00.027 *********** Pausing for 5 seconds (ctrl+C then \u0026#39;C\u0026#39; = continue early, ctrl+C then \u0026#39;A\u0026#39; = abort) ok: [localhost] TASK [timestamp demo] ******************************************************************************************** Tuesday 21 May 2024 23:47:17 +0900 (0:00:05.020) 0:00:05.048 *********** ok: [localhost] =\u0026gt; msg: Hello Ansible !! TASK [timestamp demo] ******************************************************************************************** Tuesday 21 May 2024 23:47:17 +0900 (0:00:00.016) 0:00:05.065 *********** Pausing for 5 seconds (ctrl+C then \u0026#39;C\u0026#39; = continue early, ctrl+C then \u0026#39;A\u0026#39; = abort) ok: [localhost] PLAY RECAP ******************************************************************************************************* localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 Tuesday 21 May 2024 23:47:22 +0900 (0:00:05.021) 0:00:10.087 *********** =============================================================================== timestamp demo -------------------------------------------------------------------------------------------- 5.02s timestamp demo -------------------------------------------------------------------------------------------- 5.02s timestamp demo -------------------------------------------------------------------------------------------- 0.02s timestamp demo -------------------------------------------------------------------------------------------- 0.02s また、Ansible ではなく AWX では、ジョブテンプレートのログの画面で、ヘッダ行にタイムスタンプが表示される仕様です。この仕様をコールバックプラグインとして実装したのが今回のプラグインとも言えます。\n本プラグインのしくみ ヘッダ行は、CallbackModule クラスのインスタンス変数 _display の中身、Display クラスの banner() によって出力されています。\nそこで本プラグインは、タイムスタンプを右端に表示するようにカスタマイズした banner() を新しく定義して、それをデフォルトのコールバックプラグインの _display の中の banner() と置き換えています。\n参考リンク Ansible Community Documentation の community.general.timestamp のページ まだ latest に降ってきていないので、リンク先は devel です Ansible Galaxy の community.general.timestamp のページ community.general.timestamp のソースコード プルリクエスト 開発の動機のひとつになった Forum の投稿 おわりに ふと思い立って作ったものですが、実装は強引なものの、見かけ上はシンプルでよいものができた感触です。\nEE で動かせないのがかなしいですが、ゆくゆくはどうにかしたいところです。\n","date":"2024-05-21T15:05:03Z","image":"/archives/5276/img/image-403.png","permalink":"/archives/5276/","title":"Ansible の出力にシンプルなタイムスタンプを表示する（community.general.timestamp）"},{"content":"はじめに 前回のエントリ で、vSphere に古くから実装されている API である vSphere Web Services API（SOAP API）の触り方を紹介しました。その中で、Virtual Infrastructure JSON API にひとことだけ触れています。\nSOAP API を（広義の）REST API のように触れる Virtual Infrastructure JSON API も vCenter Server 8.0 U1 以降が必要で、まだまだ一般的ではありません。 vSphere Web Services API（SOAP API）の触り方 | kurokobo.com\rVirtual Infrastructure JSON API（VI/JSON API）は、vCenter Server 8.0 U1 で追加された新しい API で、ひとことで雑に表現するなら、従来の SOAP API の REST API 版 です。\n本エントリでは、まだ日本語ではほとんど情報がないこの新しい API について、実例とともに簡単に紹介します。\nなお、vSphere の REST API といえば vSphere Automation API が一般的ですが、VI/JSON API はこれとはまったく別物 です。\n基本的な考え方 概要 Virtual Infrastructure JSON API（VI/JSON API）は、前述の通り、ひとことでは 従来の SOAP API の REST API 版 で、次の特徴を持ちます。\nSOAP API と同じエンドポイントを利用する SOAP API と同じ API が公開されている（後方互換性がある） XML ではなく JSON でやりとりする Cookie ではなく専用の HTTP ヘッダで認証する 管理対象オブジェクトのプロパティを直接参照できる（新機能、後述） SOAP API と後方互換性がある ように設計されているため、従来の SOAP API を利用したクライアントが、ロジックを維持したまま REST API での操作に移行 する目的で利用できます。ただしこの場合は、お作法が REST API に変わるだけで、実行すべきメソッドやその順序、結果から拾うべき値などが SOAP API のときと変わるわけではない点には注意が必要です。この意味で、従来の SOAP API の操作に習熟することは、VI/JSON API を触る上でも重要です。\n一方で、後述の実例で紹介しますが、管理対象オブジェクトのプロパティの取得 が PropertyCollector を使わずに直接 GET できる 点など、後発ならではの機能強化もされており、この点は VI/JSON API の明らかな強みと言えます。\n実行イメージ VI/JSON API の操作感は非常にシンプルで、雑にいうとイマドキです。\n例えば、SOAP API で仮想マシンの電源を入れる 操作をしたい場合、VirtualMachine オブジェクトの PowerOnVM_Task を実行 するため、curl では次のコマンドが必要でした（認証済みかつ管理対象オブジェクト ID は特定済みのものとしています）。\n$ WSAPI_URL=https://kuro-vcs01.kurokobo.internal/sdk $ curl \\ -sX POST ${WSAPI_URL} \\ -b ./cookies.txt \\ -H \u0026#39;content-type: text/xml; charset=\u0026#34;utf-8\u0026#34;\u0026#39; \\ -H \u0026#39;soapaction: urn:vim25/8.0.2.0\u0026#39; \\ -d @- \u0026lt;\u0026lt; EOF \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;Envelope xmlns=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34;\u0026gt; \u0026lt;Body\u0026gt; \u0026lt;PowerOnVM_Task xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;_this type=\u0026#34;VirtualMachine\u0026#34;\u0026gt;vm-14011\u0026lt;/_this\u0026gt; \u0026lt;/PowerOnVM_Task\u0026gt; \u0026lt;/Body\u0026gt; \u0026lt;/Envelope\u0026gt; EOF 同じ VirtualMachine オブジェクトの PowerOnVM_Task の実行 を、今度は VI/JSON API で行う 場合、コマンドは次の通りです。この操作ではリクエストボディは空 です。\n$ JSONAPI_URL=https://kuro-vcs01.kurokobo.internal/sdk/vim25/8.0.2.0/VirtualMachine/vm-14011/PowerOnVM_Task $ curl \\ -sX POST ${JSONAPI_URL} \\ -H \u0026#34;vmware-api-session-id: ${JSONAPI_SESSION_ID}\u0026#34; 両者を見比べると、与えているパラメータは同じ ですが、指定方法が異なっている ことが読み取れます。\nリクエストボディの XML で指定していた 操作対象やメソッド名 は URL で指定 VirtualMachine、vm-14011、PowerOnVM_Task リクエストヘッダで渡していたバージョン情報も URL で指定 8.0.2.0 Cookie で渡していた 認証情報 は リクエストヘッダで指定 vmware-api-session-id 公式ドキュメント Virtual Infrastructure JSON API に関する公式のドキュメントとして、リファレンスのほか、前回のエントリ でも紹介したプログラミングガイドの中に、8.0 U1 以降は VI/JSON API についての章が追加されています。\nvSphere Management SDK（関連ドキュメントの一覧） Virtual Infrastructure JSON API - API Reference vSphere Web Services SDK Programming Guide (8.0U2) vSphere Web Services SDK Programming Guide (8.0U2) - Client Applications Using the JSON Protocol また、VI/JSON API が実装された当初、VMWARE {code} の公式ブログ でも紹介されていました。概要から実例まで紹介されており、併せて参考にできます。\nIntroducing VI/JSON - a modern wire protocol for vSphere management - VMware {code} Automating vSphere Workflows with VI/JSON - VMware {code} Integrating VI/JSON in pyVmomi scripts - VMware {code} Read vCenter inventory using VI/JSON API - VMware {code} 参考： リクエスト URL の組み立て VI/JSON API では、操作したい管理対象オブジェクトと実行したいメソッドは、URL で指定します。ガイドでも紹介 されていますが、構文は次の通りです。\nhttps://[FQDN]/sdk/vim25/[バージョン]/[型]/[管理対象オブジェクト ID]/[メソッド] これは、SOAP API の HTTP リクエスト中のそれぞれ以下に対応します。\nPOST /sdk HTTP/2 ... soapaction: urn:vim25/[バージョン] ... \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;Envelope xmlns=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34;\u0026gt; \u0026lt;Body\u0026gt; \u0026lt;[メソッド] xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;_this type=\u0026#34;[型]\u0026#34;\u0026gt;[管理対象オブジェクト ID]\u0026lt;/_this\u0026gt; \u0026lt;/[メソッド]\u0026gt; ... \u0026lt;/Body\u0026gt; \u0026lt;/Envelope\u0026gt; 参考： リクエストボディの組み立て VI/JSON API では、メソッドに渡すパラメータをリクエストボディに JSON 形式で含めます。ガイドでも紹介 されています。\n{ \u0026#34;\u0026lt;パラメータ名\u0026gt;\u0026#34;: \u0026lt;値\u0026gt; } ただし、値が SOAP API の WSDL で定義されている型のオブジェクトの場合、都度 _typeName キーで型名を明示します。\n例えば、PropertyFilterSpec 型のプロパティ propSet は、XML では次のように組み立てられます。\n\u0026lt;propSet\u0026gt; \u0026lt;type\u0026gt;VirtualMachine\u0026lt;/type\u0026gt; \u0026lt;pathSet\u0026gt;name\u0026lt;/pathSet\u0026gt; \u0026lt;/propSet\u0026gt; これを JSON で表すと次のようになります。リファレンス からプロパティ propSet が PropertySpec 型のオブジェクトの配列であることがわかるため、配列 [] とし、_typeName で PropertySpec を与えます。\n{ \u0026#34;propSet\u0026#34;: [ { \u0026#34;_typeName\u0026#34;: \u0026#34;PropertySpec\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;VirtualMachine\u0026#34;, \u0026#34;pathSet\u0026#34;: [\u0026#34;name\u0026#34;] } ] } すべてのオブジェクトを XML から JSON に手で書き換えていくのは非常に大変です。このため、リファレンスのオブジェクトやメソッドごとのページでも JSON の例が示されている ほか、XML と JSON を相互変換する Transcoder API も用意されています。Transcoder API の使い方は本エントリの末尾で紹介しています。\n本エントリで紹介する実例 VI/JSON API を操作する実例として、前回のエントリ で紹介したシナリオのうち、仮想マシンの設定の取得 を取り上げます。ただし、VI/SOAP API の特性から、このシナリオは次の二つのパタンで実現できるため、それぞれ紹介します。\n従来の SOAP API の操作をそのまま VI/JSON API に置き換える場合 VI/JSON API のネイティブの方法を使う場合 いずれにせよ、認証してセッションを作成する操作 と 対象の管理対象オブジェクト ID を特定する操作 は必須のため、まずはこれを行います。\nなお、以降の全ての例で次の変数は共通です。\nJSONAPI_HOST=\u0026#34;kuro-vcs01.kurokobo.internal\u0026#34; JSONAPI_RELEASE=\u0026#34;8.0.2.0\u0026#34; 前提 (1)： 認証とセッションの作成 認証には SOAP API のときと同様に SessionManager の Login を実行しますが、まずは SessionManager の特定が必要です。\nSOAP API の手法に従う場合、ServiceInstance の RetrieveServiceContent に POST して確認します。\n$ JSONAPI_SI_MOID=\u0026#34;ServiceInstance\u0026#34; $ JSONAPI_SM_MOID=$( \\ curl \\ -sX POST \\ https://${JSONAPI_HOST}/sdk/vim25/${JSONAPI_RELEASE}/ServiceInstance/${JSONAPI_SI_MOID}/RetrieveServiceContent \\ | jq -r .sessionManager.value \\ | xargs -t0 \\ ) $ echo \u0026#34;${JSONAPI_SM_MOID}\u0026#34; SessionManager VI/JSON API ネイティブの方法として、ServiceInstance の content を GET することでも同様の結果が得られます。\n$ JSONAPI_SI_MOID=\u0026#34;ServiceInstance\u0026#34; $ JSONAPI_SM_MOID=$( \\ curl \\ -sX GET \\ https://${JSONAPI_HOST}/sdk/vim25/${JSONAPI_RELEASE}/ServiceInstance/${JSONAPI_SI_MOID}/content \\ | jq -r .sessionManager.value \\ | xargs -t0 \\ ) $ echo \u0026#34;${JSONAPI_SM_MOID}\u0026#34; SessionManager 特定できた SessionManager の ID を URL で指定して、Login を実行します。レスポンスヘッダで vmware-api-session-id が返ってくるため、後続の操作に備えてこの値を保存します。\n$ JSONAPI_USERNAME=\u0026#34;wsapi@vsphere.local\u0026#34; $ JSONAPI_PASSWORD=\u0026#34;VMware123!\u0026#34; $ JSONAPI_LOCALE=\u0026#34;en_US\u0026#34; $ JSONAPI_SESSION_ID=$( \\ curl \\ -siX POST \\ https://${JSONAPI_HOST}/sdk/vim25/${JSONAPI_RELEASE}/SessionManager/${JSONAPI_SM_MOID}/Login \\ -H \u0026#34;content-type: application/json\u0026#34; \\ -d @- \u0026lt;\u0026lt; EOF \\ | grep \u0026#34;vmware-api-session-id\u0026#34; | tr -d \u0026#34;\\r\u0026#34; | cut -d \u0026#34; \u0026#34; -f 2 \\ | xargs -t0 { \u0026#34;userName\u0026#34;: \u0026#34;${JSONAPI_USERNAME}\u0026#34;, \u0026#34;password\u0026#34;: \u0026#34;${JSONAPI_PASSWORD}\u0026#34;, \u0026#34;locale\u0026#34;: \u0026#34;${JSONAPI_LOCALE}\u0026#34; } EOF ) $ echo \u0026#34;${JSONAPI_SESSION_ID}\u0026#34; 61c2623dfc43bf7dc910205a57b50db9cb19a195 なお、ヘッダ vmware-api-session-id は VI/JSON API ではなく vSphere Automation API（いわゆる REST API）でも利用していますが、セッション ID は API ごとに独立しているため、使いまわしはできません。\n前提 (2)： 操作する管理対象オブジェクトの特定 この流れは SOAP API のときと同様です。今回も仮想マシン kuro-exec01 の管理対象オブジェクト ID を調べるものとします。\nまずは ViewManager の CreateContainerView で ContainerView を作成します。\n# ViewManager の ID の特定 $ JSONAPI_VIEWMANAGER_MOID=$( \\ curl \\ -sX POST \\ https://${JSONAPI_HOST}/sdk/vim25/${JSONAPI_RELEASE}/ServiceInstance/${JSONAPI_SI_MOID}/RetrieveServiceContent \\ | jq -r .viewManager.value \\ | xargs -t0 \\ ) $ echo \u0026#34;${JSONAPI_VIEWMANAGER_MOID}\u0026#34; ViewManager # インベントリのルートフォルダの特定 $ JSONAPI_ROOT_FOLDER=$( \\ curl \\ -sX POST \\ https://${JSONAPI_HOST}/sdk/vim25/${JSONAPI_RELEASE}/ServiceInstance/${JSONAPI_SI_MOID}/RetrieveServiceContent \\ | jq -r .rootFolder \\ | xargs -t0 \\ ) $ echo \u0026#34;${JSONAPI_ROOT_FOLDER}\u0026#34; { \u0026#34;_typeName\u0026#34;: \u0026#34;ManagedObjectReference\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;group-d1\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;Folder\u0026#34; } # ContainerView の作成 $ JSONAPI_CONTAINER_VIEW=$( \\ curl \\ -sX POST \\ https://${JSONAPI_HOST}/sdk/vim25/${JSONAPI_RELEASE}/ViewManager/${JSONAPI_VIEWMANAGER_MOID}/CreateContainerView \\ -H \u0026#34;content-type: application/json\u0026#34; \\ -H \u0026#34;vmware-api-session-id: ${JSONAPI_SESSION_ID}\u0026#34; \\ -d @- \u0026lt;\u0026lt; EOF \\ | xargs -t0 { \u0026#34;container\u0026#34;: ${JSONAPI_ROOT_FOLDER}, \u0026#34;recursive\u0026#34;: true, \u0026#34;type\u0026#34;: [\u0026#34;VirtualMachine\u0026#34;] } EOF ) $ echo \u0026#34;${JSONAPI_CONTAINER_VIEW}\u0026#34; { \u0026#34;_typeName\u0026#34;: \u0026#34;ManagedObjectReference\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;session[5270677a-f371-0117-7df3-36c881fd1118]525d75e8-2e95-3187-449f-41bc9c47f780\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;ContainerView\u0026#34; } PropertyCollector の RetrievePropertiesEx を使用して仮想マシンの管理対象オブジェクト ID と名前の一覧を取得し、目的の仮想マシン kuro-exec01 の管理対象オブジェクト ID を特定します。以下の例では、レスポンスの JSON の中身をそのまま jq に渡して、jq の中で仮想マシンを検索しています。\n# PropertyCollector の ID の特定 $ JSONAPI_PROPERTYCOLLECTOR_MOID=$( \\ curl \\ -sX POST \\ https://${JSONAPI_HOST}/sdk/vim25/${JSONAPI_RELEASE}/ServiceInstance/${JSONAPI_SI_MOID}/RetrieveServiceContent \\ | jq -r .propertyCollector.value \\ | xargs -t0 \\ ) $ echo \u0026#34;${JSONAPI_PROPERTYCOLLECTOR_MOID}\u0026#34; propertyCollector # 仮想マシンの ID と名前の一覧の取得 $ JSONAPI_TARGET_VM=\u0026#34;kuro-exec01\u0026#34; $ JSONAPI_VM_MOID=$( \\ curl \\ -sX POST \\ https://${JSONAPI_HOST}/sdk/vim25/${JSONAPI_RELEASE}/PropertyCollector/${JSONAPI_PROPERTYCOLLECTOR_MOID}/RetrievePropertiesEx \\ -H \u0026#34;content-type: application/json\u0026#34; \\ -H \u0026#34;vmware-api-session-id: ${JSONAPI_SESSION_ID}\u0026#34; \\ -d @- \u0026lt;\u0026lt; EOF \\ | jq -r --arg VM \u0026#34;${JSONAPI_TARGET_VM}\u0026#34; \u0026#39;.objects[] | select (.propSet[].val._value == $VM) | .obj.value\u0026#39; { \u0026#34;specSet\u0026#34;: [ { \u0026#34;_typeName\u0026#34;: \u0026#34;PropertyFilterSpec\u0026#34;, \u0026#34;propSet\u0026#34;: [ { \u0026#34;_typeName\u0026#34;: \u0026#34;PropertySpec\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;VirtualMachine\u0026#34;, \u0026#34;pathSet\u0026#34;: [\u0026#34;name\u0026#34;] } ], \u0026#34;objectSet\u0026#34;: [ { \u0026#34;_typeName\u0026#34;: \u0026#34;ObjectSpec\u0026#34;, \u0026#34;obj\u0026#34;: ${JSONAPI_CONTAINER_VIEW}, \u0026#34;skip\u0026#34;: true, \u0026#34;selectSet\u0026#34;: [ { \u0026#34;_typeName\u0026#34;: \u0026#34;TraversalSpec\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;ContainerView\u0026#34;, \u0026#34;path\u0026#34;: \u0026#34;view\u0026#34; } ] } ] } ], \u0026#34;options\u0026#34;: { \u0026#34;_typeName\u0026#34;: \u0026#34;RetrieveOptions\u0026#34;, \u0026#34;maxObjects\u0026#34;: 1000 } } EOF ) $ echo \u0026#34;${JSONAPI_VM_MOID}\u0026#34; vm-14011 使い終わった ContainerView は、ContainerView の DestroyView で削除します。\n$ JSONAPI_CONTAINER_VIEW_MOID_ENCODED=$(echo \u0026#34;${JSONAPI_CONTAINER_VIEW}\u0026#34; | jq -r \u0026#34;.value | @uri\u0026#34;) $ curl \\ -sX POST \\ https://${JSONAPI_HOST}/sdk/vim25/${JSONAPI_RELEASE}/ContainerView/${JSONAPI_CONTAINER_VIEW_MOID_ENCODED}/DestroyView \\ -H \u0026#34;vmware-api-session-id: ${JSONAPI_SESSION_ID}\u0026#34; 実例： 仮想マシンの設定を取得する（SOAP API 互換） 仮想マシンの設定のうち vmOpNotificationTimeout の値を確認したいものとして、まずは従来の SOAP API を置き換える場合の実例です。\nこの場合は、PropertyCollector の RetrievePropertiesEx を利用します。\n$ JSONAPI_VM_OP_NOTIFICATION_TIMEOUT=$( \\ curl \\ -sX POST \\ https://${JSONAPI_HOST}/sdk/vim25/${JSONAPI_RELEASE}/PropertyCollector/${JSONAPI_PROPERTYCOLLECTOR_MOID}/RetrievePropertiesEx \\ -H \u0026#34;content-type: application/json\u0026#34; \\ -H \u0026#34;vmware-api-session-id: ${JSONAPI_SESSION_ID}\u0026#34; \\ -d @- \u0026lt;\u0026lt; EOF \\ | jq -r .objects[].propSet[].val.vmOpNotificationTimeout { \u0026#34;specSet\u0026#34;: [ { \u0026#34;_typeName\u0026#34;: \u0026#34;PropertyFilterSpec\u0026#34;, \u0026#34;propSet\u0026#34;: [ { \u0026#34;_typeName\u0026#34;: \u0026#34;PropertySpec\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;VirtualMachine\u0026#34;, \u0026#34;pathSet\u0026#34;: [\u0026#34;config\u0026#34;] } ], \u0026#34;objectSet\u0026#34;: [ { \u0026#34;_typeName\u0026#34;: \u0026#34;ObjectSpec\u0026#34;, \u0026#34;obj\u0026#34;: { \u0026#34;_typeName\u0026#34;: \u0026#34;ManagedObjectReference\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;${JSONAPI_VM_MOID}\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;VirtualMachine\u0026#34; }, \u0026#34;skip\u0026#34;: false } ] } ], \u0026#34;options\u0026#34;: { \u0026#34;_typeName\u0026#34;: \u0026#34;RetrieveOptions\u0026#34; } } EOF ) $ echo \u0026#34;${JSONAPI_VM_OP_NOTIFICATION_TIMEOUT}\u0026#34; 300 実例： 仮想マシンの設定を取得する（VI/JSON API ネイティブ） 同じ設定を VI/JSON API ネイティブの方法で確認する場合、VirtualMachine の config を GET するだけです。\n$ JSONAPI_VM_OP_NOTIFICATION_TIMEOUT=$( \\ curl \\ -sX GET \\ https://${JSONAPI_HOST}/sdk/vim25/${JSONAPI_RELEASE}/VirtualMachine/${JSONAPI_VM_MOID}/config \\ -H \u0026#34;vmware-api-session-id: ${JSONAPI_SESSION_ID}\u0026#34; \\ | jq -r .vmOpNotificationTimeout ) $ echo \u0026#34;${JSONAPI_VM_OP_NOTIFICATION_TIMEOUT}\u0026#34; 300 補足： XML と JSON の相互変換 SOAP API を VI/JSON API に置き換えていく際、XML を JSON に書き換えたり、逆に JSON を XML に書き換えたりする必要が生じます。\nこの目的で、オブジェクトの XML と JSON を相互に変換できる Transcoder API が 8.0 U2 で実装されました。\n例えば、SOAP API の PropertyCollector オブジェクトの RetrievePropertiesEx メソッド には、パラメータ specSet として PropertyFilterSpec 型のオブジェクトを渡していました。XML では、次のハイライト部分（7-18 行）です。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;Envelope xmlns=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34;\u0026gt; \u0026lt;Body\u0026gt; \u0026lt;RetrievePropertiesEx xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;_this type=\u0026#34;PropertyCollector\u0026#34;\u0026gt;propertyCollector\u0026lt;/_this\u0026gt; \u0026lt;specSet\u0026gt; \u0026lt;propSet\u0026gt; \u0026lt;type\u0026gt;VirtualMachine\u0026lt;/type\u0026gt; \u0026lt;pathSet\u0026gt;name\u0026lt;/pathSet\u0026gt; \u0026lt;/propSet\u0026gt; \u0026lt;objectSet\u0026gt; \u0026lt;obj type=\u0026#34;ContainerView\u0026#34;\u0026gt;session[527102d1-b0db-1b07-ab00-e545fc76ca41]5253a3b5-b708-89eb-9c36-fec2253787f4\u0026lt;/obj\u0026gt; \u0026lt;skip\u0026gt;true\u0026lt;/skip\u0026gt; \u0026lt;selectSet xmlns:XMLSchema-instance=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; XMLSchema-instance:type=\u0026#34;TraversalSpec\u0026#34;\u0026gt; \u0026lt;type\u0026gt;ContainerView\u0026lt;/type\u0026gt; \u0026lt;path\u0026gt;view\u0026lt;/path\u0026gt; \u0026lt;/selectSet\u0026gt; \u0026lt;/objectSet\u0026gt; \u0026lt;/specSet\u0026gt; \u0026lt;options\u0026gt; \u0026lt;maxObjects\u0026gt;1000\u0026lt;/maxObjects\u0026gt; \u0026lt;/options\u0026gt; \u0026lt;/RetrievePropertiesEx\u0026gt; \u0026lt;/Body\u0026gt; \u0026lt;/Envelope\u0026gt; この specSet を Transcoder API で JSON に変換したいときは、次のリクエストを実行します。変換の方向をヘッダ content-type と accept で指定し、リクエストボディとして変換したい文字列を渡しています。変換元の型名は xsi:type で明示します。レスポンスで変換された JSON 文字列が返ってきます。\n$ JSONAPI_SPECSET_JSON=$( \\ curl \\ -sX POST \\ https://${JSONAPI_HOST}/sdk/vim25/${JSONAPI_RELEASE}/transcoder \\ -H \u0026#39;content-type: application/xml\u0026#39; \\ -H \u0026#39;accept: application/json\u0026#39; \\ -H \u0026#34;vmware-api-session-id: ${JSONAPI_SESSION_ID}\u0026#34; \\ -d @- \u0026lt;\u0026lt; EOF \u0026lt;obj versionId=\u0026#34;2.0\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xmlns:xsd=\u0026#34;http://www.w3.org/2001/XMLSchema\u0026#34; xmlns=\u0026#34;urn:vim25\u0026#34; xsi:type=\u0026#34;PropertyFilterSpec\u0026#34;\u0026gt; \u0026lt;propSet\u0026gt; \u0026lt;type\u0026gt;VirtualMachine\u0026lt;/type\u0026gt; \u0026lt;pathSet\u0026gt;name\u0026lt;/pathSet\u0026gt; \u0026lt;/propSet\u0026gt; \u0026lt;objectSet\u0026gt; \u0026lt;obj type=\u0026#34;ContainerView\u0026#34;\u0026gt;session[527102d1-b0db-1b07-ab00-e545fc76ca41]5253a3b5-b708-89eb-9c36-fec2253787f4\u0026lt;/obj\u0026gt; \u0026lt;skip\u0026gt;true\u0026lt;/skip\u0026gt; \u0026lt;selectSet xmlns:XMLSchema-instance=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; XMLSchema-instance:type=\u0026#34;TraversalSpec\u0026#34;\u0026gt; \u0026lt;type\u0026gt;ContainerView\u0026lt;/type\u0026gt; \u0026lt;path\u0026gt;view\u0026lt;/path\u0026gt; \u0026lt;/selectSet\u0026gt; \u0026lt;/objectSet\u0026gt; \u0026lt;/obj\u0026gt; EOF ) $ echo \u0026#34;${JSONAPI_SPECSET_JSON}\u0026#34; { \u0026#34;_typeName\u0026#34;: \u0026#34;PropertyFilterSpec\u0026#34;, \u0026#34;propSet\u0026#34;: [ { \u0026#34;_typeName\u0026#34;: \u0026#34;PropertySpec\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;VirtualMachine\u0026#34;, \u0026#34;pathSet\u0026#34;: [\u0026#34;name\u0026#34;] } ], \u0026#34;objectSet\u0026#34;: [ { \u0026#34;_typeName\u0026#34;: \u0026#34;ObjectSpec\u0026#34;, \u0026#34;obj\u0026#34;: { \u0026#34;_typeName\u0026#34;: \u0026#34;ManagedObjectReference\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;session[527102d1-b0db-1b07-ab00-e545fc76ca41]5253a3b5-b708-89eb-9c36-fec2253787f4\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;ContainerView\u0026#34; }, \u0026#34;skip\u0026#34;: true, \u0026#34;selectSet\u0026#34;: [ { \u0026#34;_typeName\u0026#34;: \u0026#34;TraversalSpec\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;ContainerView\u0026#34;, \u0026#34;path\u0026#34;: \u0026#34;view\u0026#34; } ] } ] } 逆に JSON を変換して XML にもできます。例として、上の例で返ってきた JSON をそのまま渡して XML にします（変換の方向が逆なので、content-type と accept の値を入れ替えています）。\n$ JSONAPI_SPECSET_XML=$( \\ curl \\ -sX POST \\ https://${JSONAPI_HOST}/sdk/vim25/${JSONAPI_RELEASE}/transcoder \\ -H \u0026#34;content-type: application/json\u0026#34; \\ -H \u0026#39;accept: application/xml\u0026#39; \\ -H \u0026#34;vmware-api-session-id: ${JSONAPI_SESSION_ID}\u0026#34; \\ -d @- \u0026lt;\u0026lt; EOF { \u0026#34;_typeName\u0026#34;: \u0026#34;PropertyFilterSpec\u0026#34;, \u0026#34;propSet\u0026#34;: [ { \u0026#34;_typeName\u0026#34;: \u0026#34;PropertySpec\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;VirtualMachine\u0026#34;, \u0026#34;pathSet\u0026#34;: [\u0026#34;name\u0026#34;] } ], \u0026#34;objectSet\u0026#34;: [ { \u0026#34;_typeName\u0026#34;: \u0026#34;ObjectSpec\u0026#34;, \u0026#34;obj\u0026#34;: { \u0026#34;_typeName\u0026#34;: \u0026#34;ManagedObjectReference\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;session[527102d1-b0db-1b07-ab00-e545fc76ca41]5253a3b5-b708-89eb-9c36-fec2253787f4\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;ContainerView\u0026#34; }, \u0026#34;skip\u0026#34;: true, \u0026#34;selectSet\u0026#34;: [ { \u0026#34;_typeName\u0026#34;: \u0026#34;TraversalSpec\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;ContainerView\u0026#34;, \u0026#34;path\u0026#34;: \u0026#34;view\u0026#34; } ] } ] } EOF ) $ echo \u0026#34;${JSONAPI_SPECSET_XML}\u0026#34; | xmllint --format - \u0026lt;?xml version=\u0026#34;1.0\u0026#34;?\u0026gt; \u0026lt;obj xmlns:xsd=\u0026#34;http://www.w3.org/2001/XMLSchema\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xmlns=\u0026#34;urn:vim25\u0026#34; versionId=\u0026#34;8.0.2.0\u0026#34; xsi:type=\u0026#34;PropertyFilterSpec\u0026#34;\u0026gt; \u0026lt;propSet\u0026gt; \u0026lt;type\u0026gt;VirtualMachine\u0026lt;/type\u0026gt; \u0026lt;pathSet\u0026gt;name\u0026lt;/pathSet\u0026gt; \u0026lt;/propSet\u0026gt; \u0026lt;objectSet\u0026gt; \u0026lt;obj type=\u0026#34;ContainerView\u0026#34;\u0026gt;session[527102d1-b0db-1b07-ab00-e545fc76ca41]5253a3b5-b708-89eb-9c36-fec2253787f4\u0026lt;/obj\u0026gt; \u0026lt;skip\u0026gt;true\u0026lt;/skip\u0026gt; \u0026lt;selectSet xsi:type=\u0026#34;TraversalSpec\u0026#34;\u0026gt; \u0026lt;type\u0026gt;ContainerView\u0026lt;/type\u0026gt; \u0026lt;path\u0026gt;view\u0026lt;/path\u0026gt; \u0026lt;/selectSet\u0026gt; \u0026lt;/objectSet\u0026gt; \u0026lt;/obj\u0026gt; なお、Transcoder API の利用にはコマンド例のとおり認証が必要ですが、SOAP API クライアントからの利用をしやすくするため、Transcoder API だけは SOAP API と同じ Cookie を利用した認証も許容されているようです。\n補足： curl でヘッダを取り出すときの考慮点 curl に限定した余談です。\ncurl でレスポンスヘッダから値を拾うとき、-v や -I でヘッダを標準出力に吐かせてから grep するパタンがよく使われますが、このとき、curl はサーバと送受信したヘッダをそのまま出力するようです。つまり、行末が CR+LF になっています。\n# レスポンスヘッダの content-type を拾う例 $ curl -sI --no-styled-output www.example.com \\ | grep -i --color=never content-type Content-Type: text/html; charset=UTF-8 # バイナリダンプすると行末に CR+LF（0x0d, 0x0a）があることがわかる $ curl -sI --no-styled-output www.example.com \\ | grep -i --color=never content-type \\ | od -tx1z -Ax 000000 43 6f 6e 74 65 6e 74 2d 54 79 70 65 3a 20 74 65 \u0026gt;Content-Type: te\u0026lt; 000010 78 74 2f 68 74 6d 6c 3b 20 63 68 61 72 73 65 74 \u0026gt;xt/html; charset\u0026lt; 000020 3d 55 54 46 2d 38 0d 0a \u0026gt;=UTF-8..\u0026lt; 000028 このため、grep 後に単純に cut や awk で雑に値部分を抽出すると、行末の CR も含まれてしまいます。\n# awk で値部分を取り出して格納 $ HEADER=$( \\ curl -sI --no-styled-output www.example.com \\ | grep -i --color=never content-type \\ | awk -F \u0026#39;: \u0026#39; \u0026#39;{print $2}\u0026#39; \\ ) # それっぽい切り出しができていそうに見える $ echo \u0026#34;${HEADER}\u0026#34; text/html; charset=UTF-8 # 実際には末尾に CR（0x0d）が残っている $ echo -n \u0026#34;${HEADER}\u0026#34; | od -tx1z -Ax 000000 74 65 78 74 2f 68 74 6d 6c 3b 20 63 68 61 72 73 \u0026gt;text/html; chars\u0026lt; 000010 65 74 3d 55 54 46 2d 38 0d \u0026gt;et=UTF-8.\u0026lt; 000019 使途次第では無視できますが、少なくとも VI/JSON API では、ヘッダに埋め込むセッション ID の末尾に CR が残った状態でリクエストしても正常に処理されず、レスポンスが得られません。\nこのため、本エントリのコマンド例では、セッション ID を取り出すコマンドの grep のあとに tr -d \u0026quot;\\r\u0026quot; を入れて、CR を明示的に取り除いています。\n# CR を取り除く $ HEADER=$( \\ curl -sI --no-styled-output www.example.com \\ | grep -i --color=never content-type \\ | tr -d \u0026#34;\\r\u0026#34; \\ | awk -F \u0026#39;: \u0026#39; \u0026#39;{print $2}\u0026#39; \\ ) # 末尾の CR がなくなる $ echo -n \u0026#34;${HEADER}\u0026#34; | od -tx1z -Ax 000000 74 65 78 74 2f 68 74 6d 6c 3b 20 63 68 61 72 73 \u0026gt;text/html; chars\u0026lt; 000010 65 74 3d 55 54 46 2d 38 \u0026gt;et=UTF-8\u0026lt; 000018 おわりに VI/JSON API は、従来の SOAP API のクライアントを REST API ベースの実装に移行できるだけでなく、PropertyCollector を介さずに直接 GET できるオブジェクトが多数用意されており、少なくとも XML と格闘するよりは圧倒的に直感的に操作できます。\nvCenter Server 8.0 U1 以降が必要にはなりますが、SOAP API の操作にハードルを感じている場合、VI/JSON API がよい代替手段となりそうです。\n利用できるメソッドはリファレンスで一覧 できるので、おもしろそうなものをとにかく触ってみるのもたのしそうです。\n","date":"2024-03-09T07:55:15Z","image":"/archives/5229/img/image-400.png","permalink":"/archives/5229/","title":"Virtual Infrastructure JSON API（VI/JSON API）の触り方"},{"content":"はじめに vSphere 環境をコマンドライン環境や何らかのスクリプトから操作するとき、典型的には PowerCLI や govmomi、pyVmomi、VMware vSphere Automation SDK などのツールがよく利用されますが、こうしたツールを使わずに API を直接触れる と、vSphere に実装された新機能をリリースと同時に使い始められ てたいへん便利です。実際、vSphere vMotion Notifications がリリースされたときも、ツールが更新されるまでは、API を直接触る 以外にそれを試す手段がありませんでした。\nvSphere の API には、古くからある SOAP API（vSphere Web Services API）と新しい REST API（vSphere Automation API）の大きく二つがあります。操作が簡単なのは間違いなく REST API ですが、SOAP API でしか行えない操作もたくさんあり、また、SOAP API を（広義の）REST API のように触れる Virtual Infrastructure JSON API も vCenter Server 8.0 U1 以降が必要で、まだまだ一般的ではありません。\nしたがって現状では、昔ながらの SOAP API こそ が、極論、大抵のことはどうにかできる API であると言えます。そこでこのエントリでは、SOAP API（vSphere Web Services API）を直接触る方法 を実例と共に紹介します。\nなお、コマンド例は curl と PowerShell で紹介 していますが、結局は HTTP で XML をやりとりするだけなので、お作法さえわかれば他の HTTP クライアントでももちろん利用できます。また、SOAP API は Managed Object Browser（MOB）でも操作できますが、Web ブラウザでの操作であり自動化には向かないため、今回は取り上げません。\n基本的な考え方 基本のお作法 vSphere Web Services API を HTTP クライアントから操作する際の基本は次の通りです。\nHTTP リクエスト 常に 同一のエンドポイント（https://\u0026lt;FQDN\u0026gt;/sdk/vimService）に対して送信する https://\u0026lt;FQDN\u0026gt;/sdk でも可 常に ヘッダに content-type と soapaction を含める content-type: text/xml; charset=\u0026quot;utf-8\u0026quot; soapaction: urn:vim25/8.0.2.0（vCenter Server のバージョンに合わせる） 常に POST する 常に リクエストボディに XML を含める HTTP レスポンス レスポンスボディの XML から情報を読み取る 認証 ユーザ名とパスワードで認証 する（SAML トークンを使った認証も可） Cookie 認証に成功すると レスポンスヘッダの set-cookie でセッション ID が返される 以降は セッション ID を Cookie に含める ことで認証済みユーザとして操作できる なお、SAML トークンを使った認証 は本エントリでは取り上げません。\n公式ドキュメント vSphere Web Services API に関する公式のドキュメントとして、リファレンスとプログラミングガイド、セットアップガイドが公開されています。\nvSphere Management SDK（関連ドキュメントの一覧） vSphere Web Services API - API Reference (8.0U2) vSphere Web Services SDK Programming Guide (8.0U2) vSphere Web Services SDK Developer Setup Guide (8.0U2) 基本的には、プログラミングガイドを読んで目的に応じた大まかな処理の流れをつかみ、文中で示されている具体的なメソッドなどをとっかかりにしてリファレンスを芋づる式に調べていくことになります。\nはじめにプログラミングガイドの vSphere Web Services API Programming Model に含まれる以下あたりも目を通しておくと、本エントリで紹介する操作の流れも追いやすくなるでしょう。\nvSphere Client-Server Architecture Managed Objects（管理対象オブジェクト、MO）と Managed Object References（管理対象オブジェクト参照、MoRef）の説明 Access to Managed Objects すべての親玉の Managed Object である ServiceInstance の説明 Access to vSphere Server Data Managed Objects に含まれるデータを取得する方法の説明 なお、目的の（またはそれに近い）操作が govc でできるのであれば、-trace オプションにより HTTP のリクエストとレスポンスがすべて標準エラー出力に吐かれるようになるので、これも参考資料として非常に強力です。詳細は govc のドキュメントに記載があります。\n参考： コマンド例（Bash / curl） Bash で curl を使う場合の実際のコマンド例です。\ncurl で XML をやりとりするとき、適切な改行やインデントで整形されているほうが多少なりとも人間にやさしくなります。このため、ここでは次の方針としています。\nリクエストボディの記述にはヒアドキュメントを利用する レスポンスボディは xmllint にパイプして整形（または必要な値を抽出）する この方針のもとでコマンドを組むと、例えば次のようになります。\n$ WSAPI_URL=https://kuro-vcs01.kurokobo.internal/sdk $ curl \\ -sX POST ${WSAPI_URL} \\ -b ./cookies.txt \\ -H \u0026#39;content-type: text/xml; charset=\u0026#34;utf-8\u0026#34;\u0026#39; \\ -H \u0026#39;soapaction: urn:vim25/8.0.2.0\u0026#39; \\ -d @- \u0026lt;\u0026lt; EOF | xmllint --format - \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;Envelope xmlns=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34;\u0026gt; \u0026lt;Body\u0026gt; ... \u0026lt;/Body\u0026gt; \u0026lt;/Envelope\u0026gt; EOF レスポンスボディから（整形せずに）値を直接取り出したい場合は、以下のように --xpath などでがんばれます。なお、レスポンスの XML は多数の名前空間で構成されているため、xmllint でのハンドリングは少々しんどさがあり、基本、試行錯誤する覚悟が必要です。\n$ WSAPI_URL=https://kuro-vcs01.kurokobo.internal/sdk $ curl \\ -sX POST ${WSAPI_URL} \\ -b ./cookies.txt \\ -H \u0026#39;content-type: text/xml; charset=\u0026#34;utf-8\u0026#34;\u0026#39; \\ -H \u0026#39;soapaction: urn:vim25/8.0.2.0\u0026#39; \\ -d @- \u0026lt;\u0026lt; EOF | xmllint --xpath \u0026#34;//*[local-name()=\u0026#39;hoge\u0026#39;]/text()\u0026#34; - \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;Envelope xmlns=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34;\u0026gt; \u0026lt;Body\u0026gt; ... \u0026lt;/Body\u0026gt; \u0026lt;/Envelope\u0026gt; EOF 参考： コマンド例（PowerShell / Invoke-WebRequest） PowerShell では、Invoke-WebRequest で操作できます。実例として vSphere vMotion Notifications を試すために作った PowerShell モジュールを GitHub で公開 しています。このモジュールでは、WSDL（本エントリ末尾で紹介）は利用せずに、XML 文字列をベタ書きして Invoke-WebRequest で操作しています。\nCookie を有効にするために、-SessionVariable と -WebSession を使っています。また、レスポンスは xml にキャストする（[xml]((Invoke-WebRequest).Content)）と扱いやすくなります。画面に XML を出力する際に見た目を整えたい場合は、Format-XML が便利です。\n本エントリで紹介する実例 参照系と更新系のユースケースとして、本エントリでは以下の実例を紹介します。\n(1) 仮想マシンの設定を取得する (2) 仮想マシンの設定を変更する (3) ホストの詳細オプションの値を取得する (4) ホストの詳細オプションの値を変更する ただし、いずれの場合でも まずは認証済みセッションを作成 して、さらに操作する 管理対象オブジェクトの特定 が必要なため、これも含めて紹介します。\n以降、完全なコマンド例ではなく、リクエストとレスポンスの XML 部分のみ記載します。\n前提 (1)： 認証とセッションの作成 後続の操作のため、まずは認証をして認証済みセッションを作成します。\n認証には ガイドで示されている手順 の通り SessionManager オブジェクトの Login を実行しますが、前提として、使う SessionManager の ID をすべての親玉たる ServiceInstance オブジェクトの RetrieveServiceContent で調べる必要があります。これには次のリクエストを行います。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;Envelope xmlns=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34;\u0026gt; \u0026lt;Body\u0026gt; \u0026lt;RetrieveServiceContent xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;_this type=\u0026#34;ServiceInstance\u0026#34;\u0026gt;ServiceInstance\u0026lt;/_this\u0026gt; \u0026lt;/RetrieveServiceContent\u0026gt; \u0026lt;/Body\u0026gt; \u0026lt;/Envelope\u0026gt; レスポンスボディの \u0026lt;sessionManager\u0026gt; の値を確認します。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;soapenv:Envelope xmlns:soapenc=\u0026#34;http://schemas.xmlsoap.org/soap/encoding/\u0026#34; xmlns:soapenv=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34; xmlns:xsd=\u0026#34;http://www.w3.org/2001/XMLSchema\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34;\u0026gt; \u0026lt;soapenv:Body\u0026gt; \u0026lt;RetrieveServiceContentResponse xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;returnval\u0026gt; \u0026lt;rootFolder type=\u0026#34;Folder\u0026#34;\u0026gt;group-d1\u0026lt;/rootFolder\u0026gt; \u0026lt;propertyCollector type=\u0026#34;PropertyCollector\u0026#34;\u0026gt;propertyCollector\u0026lt;/propertyCollector\u0026gt; \u0026lt;viewManager type=\u0026#34;ViewManager\u0026#34;\u0026gt;ViewManager\u0026lt;/viewManager\u0026gt; ... \u0026lt;sessionManager type=\u0026#34;SessionManager\u0026#34;\u0026gt;SessionManager\u0026lt;/sessionManager\u0026gt; ... \u0026lt;/returnval\u0026gt; \u0026lt;/RetrieveServiceContentResponse\u0026gt; \u0026lt;/soapenv:Body\u0026gt; \u0026lt;/soapenv:Envelope\u0026gt; \u0026lt;sessionManager\u0026gt; の値から、使うべき SessionManager オブジェクトの ID が SessionManager と確認できるので、続けてこれを _this で渡して Login を実行します。リファレンス の通り、パラメータは _this と userName と password と locale です。locale はなくてもよいですが、明示したほうが機械的に処理する際に言語の違いで悩む心配がなくなって安心です。\nリクエストボディは以下の通りです。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;Envelope xmlns=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34;\u0026gt; \u0026lt;Body\u0026gt; \u0026lt;Login xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;_this type=\u0026#34;SessionManager\u0026#34;\u0026gt;SessionManager\u0026lt;/_this\u0026gt; \u0026lt;userName\u0026gt;wsapi@vsphere.local\u0026lt;/userName\u0026gt; \u0026lt;password\u0026gt;VMware123!\u0026lt;/password\u0026gt; \u0026lt;locale\u0026gt;en_US\u0026lt;/locale\u0026gt; \u0026lt;/Login\u0026gt; \u0026lt;/Body\u0026gt; \u0026lt;/Envelope\u0026gt; このリクエストがエラーなく完了してレスポンスが返ってくれば（ステータスコードが 200 であれば）認証は完了です。レスポンスヘッダの set-cookie で vmware_soap_session が返されるため、これを何らかの手段で保存しておきます。\n前提 (2)： 操作する管理対象オブジェクトの特定 操作の対象が 仮想マシン や ホスト、ネットワーク、データストア など、とにかく インベントリに含まれるモノ であった場合、操作対象に相当する管理対象オブジェクト（Managed Object、MO）の特定が必要です。\n人間はインベントリ内のモノをその 名前 で認識しますが、内部的にはそれぞれに 管理対象オブジェクト ID が与えられており、API での操作対象の指示にはこの ID を利用します。したがって、名前から管理対象オブジェクト ID を特定する必要があります。\n例えば、対象が 仮想マシン であった場合、これは次のような流れで実現します。ガイドでも流れが紹介 されています。\nすべての仮想マシンを含む ContainerView オブジェクトを作成する（ViewManager オブジェクトの CreateContainerView） 作成した ContainerView オブジェクトから仮想マシンの名前を取得する（PropertyCollector オブジェクトの RetrievePropertiesEx） 目的の仮想マシンの名前から管理対象オブジェクト ID を特定する ContainerView オブジェクトを削除する（ContainerView オブジェクトの DestroyView） 以下、仮想マシン kuro-exec01 の管理対象オブジェクト ID を特定するものとして、操作方法を紹介します。\nContainerView の作成 まずは、『このフォルダの中から仮想マシンを再帰的に探して集めて箱に入れておいてね』的なリクエストを送ります。ViewManager オブジェクトの CreateContainerView で、探索対象の型（VirtualMachine オブジェクト）とルートフォルダを指定して ContainerView オブジェクトを作成します。パラメータとして渡している ViewManager オブジェクトの ID とルートフォルダは、いずれも先述の ServiceInstance オブジェクトの RetrieveServiceContent の結果に含まれています。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;Envelope xmlns=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34;\u0026gt; \u0026lt;Body\u0026gt; \u0026lt;CreateContainerView xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;_this type=\u0026#34;ViewManager\u0026#34;\u0026gt;ViewManager\u0026lt;/_this\u0026gt; \u0026lt;container type=\u0026#34;Folder\u0026#34;\u0026gt;group-d1\u0026lt;/container\u0026gt; \u0026lt;type\u0026gt;VirtualMachine\u0026lt;/type\u0026gt; \u0026lt;recursive\u0026gt;true\u0026lt;/recursive\u0026gt; \u0026lt;/CreateContainerView\u0026gt; \u0026lt;/Body\u0026gt; \u0026lt;/Envelope\u0026gt; レスポンスは次の通りです。作成された ContainerView オブジェクトの情報が returnval として含まれています。この値は後続のリクエストで使用します。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;soapenv:Envelope xmlns:soapenc=\u0026#34;http://schemas.xmlsoap.org/soap/encoding/\u0026#34; xmlns:soapenv=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34; xmlns:xsd=\u0026#34;http://www.w3.org/2001/XMLSchema\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34;\u0026gt; \u0026lt;soapenv:Body\u0026gt; \u0026lt;CreateContainerViewResponse xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;returnval type=\u0026#34;ContainerView\u0026#34;\u0026gt;session[52a1d72b-a551-4c17-99a1-0214d65d4ef6]5293b717-07cc-cfd8-fd6a-d5a7b0cccb86\u0026lt;/returnval\u0026gt; \u0026lt;/CreateContainerViewResponse\u0026gt; \u0026lt;/soapenv:Body\u0026gt; \u0026lt;/soapenv:Envelope\u0026gt; 仮想マシン以外の型のオブジェクトを探索する場合でも、リクエストの \u0026lt;type\u0026gt; を変更すれば対応できます。\nRetrievePropertiesEx の実行と対象の特定 続けて、『さっきの箱の中に入っている仮想マシンの名前を調べて教えてね』的なリクエストを送ります。PropertyCollector オブジェクトの RetrievePropertiesEx で、取得したいプロパティとして VirtualMachine オブジェクトの name を指定し、探索対象として先ほどの ContainerView オブジェクトを指定します。RetrievePropertiesEx は、メソッド名の通り、対象のオブジェクトから任意のプロパティを取得するためのメソッドです。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;Envelope xmlns=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34;\u0026gt; \u0026lt;Body\u0026gt; \u0026lt;RetrievePropertiesEx xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;_this type=\u0026#34;PropertyCollector\u0026#34;\u0026gt;propertyCollector\u0026lt;/_this\u0026gt; \u0026lt;specSet\u0026gt; \u0026lt;propSet\u0026gt; \u0026lt;type\u0026gt;VirtualMachine\u0026lt;/type\u0026gt; \u0026lt;pathSet\u0026gt;name\u0026lt;/pathSet\u0026gt; \u0026lt;/propSet\u0026gt; \u0026lt;objectSet\u0026gt; \u0026lt;obj type=\u0026#34;ContainerView\u0026#34;\u0026gt;session[527102d1-b0db-1b07-ab00-e545fc76ca41]5253a3b5-b708-89eb-9c36-fec2253787f4\u0026lt;/obj\u0026gt; \u0026lt;skip\u0026gt;true\u0026lt;/skip\u0026gt; \u0026lt;selectSet xmlns:XMLSchema-instance=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; XMLSchema-instance:type=\u0026#34;TraversalSpec\u0026#34;\u0026gt; \u0026lt;type\u0026gt;ContainerView\u0026lt;/type\u0026gt; \u0026lt;path\u0026gt;view\u0026lt;/path\u0026gt; \u0026lt;/selectSet\u0026gt; \u0026lt;/objectSet\u0026gt; \u0026lt;/specSet\u0026gt; \u0026lt;options\u0026gt; \u0026lt;maxObjects\u0026gt;1000\u0026lt;/maxObjects\u0026gt; \u0026lt;/options\u0026gt; \u0026lt;/RetrievePropertiesEx\u0026gt; \u0026lt;/Body\u0026gt; \u0026lt;/Envelope\u0026gt; レスポンスは次の通りで、ルートフォルダ配下の全仮想マシンの情報が、リクエスト通り name プロパティの内容と共に返ってきます。ここから、今回操作したい仮想マシン kuro-exec01 の ID が vm-14011 であることが特定できます。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;soapenv:Envelope xmlns:soapenc=\u0026#34;http://schemas.xmlsoap.org/soap/encoding/\u0026#34; xmlns:soapenv=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34; xmlns:xsd=\u0026#34;http://www.w3.org/2001/XMLSchema\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34;\u0026gt; \u0026lt;soapenv:Body\u0026gt; \u0026lt;RetrievePropertiesExResponse xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;returnval\u0026gt; \u0026lt;objects\u0026gt; \u0026lt;obj type=\u0026#34;VirtualMachine\u0026#34;\u0026gt;vm-14011\u0026lt;/obj\u0026gt; \u0026lt;propSet\u0026gt; \u0026lt;name\u0026gt;name\u0026lt;/name\u0026gt; \u0026lt;val xsi:type=\u0026#34;xsd:string\u0026#34;\u0026gt;kuro-exec01\u0026lt;/val\u0026gt; \u0026lt;/propSet\u0026gt; \u0026lt;/objects\u0026gt; \u0026lt;objects\u0026gt; \u0026lt;obj type=\u0026#34;VirtualMachine\u0026#34;\u0026gt;vm-14012\u0026lt;/obj\u0026gt; \u0026lt;propSet\u0026gt; \u0026lt;name\u0026gt;name\u0026lt;/name\u0026gt; \u0026lt;val xsi:type=\u0026#34;xsd:string\u0026#34;\u0026gt;kuro-exec02\u0026lt;/val\u0026gt; \u0026lt;/propSet\u0026gt; \u0026lt;/objects\u0026gt; \u0026lt;objects\u0026gt; \u0026lt;obj type=\u0026#34;VirtualMachine\u0026#34;\u0026gt;vm-14013\u0026lt;/obj\u0026gt; \u0026lt;propSet\u0026gt; \u0026lt;name\u0026gt;name\u0026lt;/name\u0026gt; \u0026lt;val xsi:type=\u0026#34;xsd:string\u0026#34;\u0026gt;kuro-exec03\u0026lt;/val\u0026gt; \u0026lt;/propSet\u0026gt; \u0026lt;/objects\u0026gt; ... \u0026lt;/returnval\u0026gt; \u0026lt;/RetrievePropertiesExResponse\u0026gt; \u0026lt;/soapenv:Body\u0026gt; \u0026lt;/soapenv:Envelope\u0026gt; 仮想マシン以外の型のオブジェクトを探索する場合でも、name プロパティはどの型にもあるはずなので、ほとんど同じです。\nContainerView の削除 仮想マシンが特定できたら、作成した ContainerView オブジェクトをそれ自身の DestroyView で破棄します。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;Envelope xmlns=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34;\u0026gt; \u0026lt;Body\u0026gt; \u0026lt;DestroyView xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;_this type=\u0026#34;ContainerView\u0026#34;\u0026gt;session[527102d1-b0db-1b07-ab00-e545fc76ca41]5253a3b5-b708-89eb-9c36-fec2253787f4\u0026lt;/_this\u0026gt; \u0026lt;/DestroyView\u0026gt; \u0026lt;/Body\u0026gt; \u0026lt;/Envelope\u0026gt; 実例 (1)： 仮想マシンの設定を取得する ここから、具体的な参照操作の例として、仮想マシン kuro-exec01 の設定情報を取得することを考えます。今回は、仮想マシンの vmOpNotificationTimeout の値を確認したいものとして、これを含んでいる VirtualMachine オブジェクトの config プロパティ（VirtualMachineConfigInfo オブジェクト）を取得します。\nこれには、ガイドでも紹介 されていますが、管理対象オブジェクト ID の特定にも利用した PropertyCollector オブジェクトの RetrievePropertiesEx を再度利用します。先ほどは ContainerView オブジェクトを対象にしましたが、ここでは単一の仮想マシンが対象です。\nリクエストには、前述の手順で特定した管理対象オブジェクト ID が必要です。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;Envelope xmlns=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34;\u0026gt; \u0026lt;Body\u0026gt; \u0026lt;RetrievePropertiesEx xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;_this type=\u0026#34;PropertyCollector\u0026#34;\u0026gt;propertyCollector\u0026lt;/_this\u0026gt; \u0026lt;specSet\u0026gt; \u0026lt;propSet\u0026gt; \u0026lt;type\u0026gt;VirtualMachine\u0026lt;/type\u0026gt; \u0026lt;pathSet\u0026gt;config\u0026lt;/pathSet\u0026gt; \u0026lt;/propSet\u0026gt; \u0026lt;objectSet\u0026gt; \u0026lt;obj type=\u0026#34;VirtualMachine\u0026#34;\u0026gt;vm-14011\u0026lt;/obj\u0026gt; \u0026lt;skip\u0026gt;false\u0026lt;/skip\u0026gt; \u0026lt;/objectSet\u0026gt; \u0026lt;/specSet\u0026gt; \u0026lt;options\u0026gt;\u0026lt;/options\u0026gt; \u0026lt;/RetrievePropertiesEx\u0026gt; \u0026lt;/Body\u0026gt; \u0026lt;/Envelope\u0026gt; レスポンスとして、config プロパティの中身である VirtualMachineConfigInfo オブジェクトが返ってきます。ここから、仮想マシンの具体的な設定が読み取れます。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;soapenv:Envelope xmlns:soapenc=\u0026#34;http://schemas.xmlsoap.org/soap/encoding/\u0026#34; xmlns:soapenv=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34; xmlns:xsd=\u0026#34;http://www.w3.org/2001/XMLSchema\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34;\u0026gt; \u0026lt;soapenv:Body\u0026gt; \u0026lt;RetrievePropertiesExResponse xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;returnval\u0026gt; \u0026lt;objects\u0026gt; \u0026lt;obj type=\u0026#34;VirtualMachine\u0026#34;\u0026gt;vm-14011\u0026lt;/obj\u0026gt; \u0026lt;propSet\u0026gt; \u0026lt;name\u0026gt;config\u0026lt;/name\u0026gt; \u0026lt;val xsi:type=\u0026#34;VirtualMachineConfigInfo\u0026#34;\u0026gt; ... \u0026lt;name\u0026gt;kuro-exec01\u0026lt;/name\u0026gt; ... \u0026lt;vmOpNotificationToAppEnabled\u0026gt;false\u0026lt;/vmOpNotificationToAppEnabled\u0026gt; \u0026lt;vmOpNotificationTimeout\u0026gt;-1\u0026lt;/vmOpNotificationTimeout\u0026gt; ... \u0026lt;/val\u0026gt; \u0026lt;/propSet\u0026gt; \u0026lt;/objects\u0026gt; \u0026lt;/returnval\u0026gt; \u0026lt;/RetrievePropertiesExResponse\u0026gt; \u0026lt;/soapenv:Body\u0026gt; \u0026lt;/soapenv:Envelope\u0026gt; 目的の項目 vmOpNotificationTimeout は、値が -1 であることが確認できました。\n実例 (2)： 仮想マシンの設定を変更する 仮想マシンの設定を変更するには、VirtualMachine オブジェクトの ReconfigVM_Task を利用します。_this で ID を指定して、設定したい値を spec として渡すだけです。今回は vmOpNotificationToAppEnabled に true を設定しています。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;Envelope xmlns=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34;\u0026gt; \u0026lt;Body\u0026gt; \u0026lt;ReconfigVM_Task xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;_this type=\u0026#34;VirtualMachine\u0026#34;\u0026gt;vm-14011\u0026lt;/_this\u0026gt; \u0026lt;spec\u0026gt; \u0026lt;vmOpNotificationToAppEnabled\u0026gt;true\u0026lt;/vmOpNotificationToAppEnabled\u0026gt; \u0026lt;/spec\u0026gt; \u0026lt;/ReconfigVM_Task\u0026gt; \u0026lt;/Body\u0026gt; \u0026lt;/Envelope\u0026gt; レスポンスとして、リクエストした変更を処理する Task オブジェクトの情報が返ってきます。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;soapenv:Envelope xmlns:soapenc=\u0026#34;http://schemas.xmlsoap.org/soap/encoding/\u0026#34; xmlns:soapenv=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34; xmlns:xsd=\u0026#34;http://www.w3.org/2001/XMLSchema\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34;\u0026gt; \u0026lt;soapenv:Body\u0026gt; \u0026lt;ReconfigVM_TaskResponse xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;returnval type=\u0026#34;Task\u0026#34;\u0026gt;task-10260\u0026lt;/returnval\u0026gt; \u0026lt;/ReconfigVM_TaskResponse\u0026gt; \u0026lt;/soapenv:Body\u0026gt; \u0026lt;/soapenv:Envelope\u0026gt; 作成された Task オブジェクトの ID が task-10260 とわかるので、進捗を確認するため、これの info プロパティを取得します。使用するのは、おなじみの PropertyCollector オブジェクトの RetrievePropertiesEx です。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;Envelope xmlns=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34;\u0026gt; \u0026lt;Body\u0026gt; \u0026lt;RetrievePropertiesEx xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;_this type=\u0026#34;PropertyCollector\u0026#34;\u0026gt;propertyCollector\u0026lt;/_this\u0026gt; \u0026lt;specSet\u0026gt; \u0026lt;propSet\u0026gt; \u0026lt;type\u0026gt;Task\u0026lt;/type\u0026gt; \u0026lt;pathSet\u0026gt;info\u0026lt;/pathSet\u0026gt; \u0026lt;/propSet\u0026gt; \u0026lt;objectSet\u0026gt; \u0026lt;obj type=\u0026#34;Task\u0026#34;\u0026gt;task-10260\u0026lt;/obj\u0026gt; \u0026lt;skip\u0026gt;false\u0026lt;/skip\u0026gt; \u0026lt;/objectSet\u0026gt; \u0026lt;/specSet\u0026gt; \u0026lt;options\u0026gt;\u0026lt;/options\u0026gt; \u0026lt;/RetrievePropertiesEx\u0026gt; \u0026lt;/Body\u0026gt; \u0026lt;/Envelope\u0026gt; 進捗はレスポンスの \u0026lt;state\u0026gt; で確認できます。実行待ちの場合は queued、実行中は running、完了すると success、失敗すると error です。queued や running だった場合は、\u0026lt;status\u0026gt; が success または error になるまで RetrievePropertiesEx を繰り返し実行します。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;soapenv:Envelope xmlns:soapenc=\u0026#34;http://schemas.xmlsoap.org/soap/encoding/\u0026#34; xmlns:soapenv=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34; xmlns:xsd=\u0026#34;http://www.w3.org/2001/XMLSchema\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34;\u0026gt; \u0026lt;soapenv:Body\u0026gt; \u0026lt;RetrievePropertiesExResponse xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;returnval\u0026gt; \u0026lt;objects\u0026gt; \u0026lt;obj type=\u0026#34;Task\u0026#34;\u0026gt;task-10260\u0026lt;/obj\u0026gt; \u0026lt;propSet\u0026gt; \u0026lt;name\u0026gt;info\u0026lt;/name\u0026gt; \u0026lt;val xsi:type=\u0026#34;TaskInfo\u0026#34;\u0026gt; \u0026lt;key\u0026gt;task-10260\u0026lt;/key\u0026gt; \u0026lt;task type=\u0026#34;Task\u0026#34;\u0026gt;task-10260\u0026lt;/task\u0026gt; \u0026lt;name\u0026gt;ReconfigVM_Task\u0026lt;/name\u0026gt; \u0026lt;descriptionId\u0026gt;VirtualMachine.reconfigure\u0026lt;/descriptionId\u0026gt; \u0026lt;entity type=\u0026#34;VirtualMachine\u0026#34;\u0026gt;vm-14011\u0026lt;/entity\u0026gt; \u0026lt;entityName\u0026gt;kuro-exec01\u0026lt;/entityName\u0026gt; \u0026lt;state\u0026gt;success\u0026lt;/state\u0026gt; \u0026lt;cancelled\u0026gt;false\u0026lt;/cancelled\u0026gt; \u0026lt;cancelable\u0026gt;false\u0026lt;/cancelable\u0026gt; \u0026lt;reason xsi:type=\u0026#34;TaskReasonUser\u0026#34;\u0026gt; \u0026lt;userName\u0026gt;VSPHERE.LOCAL\\Administrator\u0026lt;/userName\u0026gt; \u0026lt;/reason\u0026gt; \u0026lt;queueTime\u0026gt;...\u0026lt;/queueTime\u0026gt; \u0026lt;startTime\u0026gt;...\u0026lt;/startTime\u0026gt; \u0026lt;completeTime\u0026gt;...\u0026lt;/completeTime\u0026gt; \u0026lt;eventChainId\u0026gt;573132\u0026lt;/eventChainId\u0026gt; \u0026lt;/val\u0026gt; \u0026lt;/propSet\u0026gt; \u0026lt;/objects\u0026gt; \u0026lt;/returnval\u0026gt; \u0026lt;/RetrievePropertiesExResponse\u0026gt; \u0026lt;/soapenv:Body\u0026gt; \u0026lt;/soapenv:Envelope\u0026gt; 実例 (3)： ホストの詳細オプションの値を取得する 続けて、ホストからの情報の取得の例です。\nここではホストの詳細オプションのうち VmOpNotificationToApp.Timeout の値を確認したいものとします。対象のホストは kuro-esxi01.kurokobo.internal で、この管理対象オブジェクト ID は host-14 です（前述の手順で HostSystem オブジェクトを対象に探索して特定します）。\n詳細オプションを調べるには、まずは PropertyCollector オブジェクトの RetrievePropertiesEx で、対象の HostSystem オブジェクトの configManager.advancedOption プロパティから OptionManager オブジェクトを取得します。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;Envelope xmlns=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34;\u0026gt; \u0026lt;Body\u0026gt; \u0026lt;RetrievePropertiesEx xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;_this type=\u0026#34;PropertyCollector\u0026#34;\u0026gt;propertyCollector\u0026lt;/_this\u0026gt; \u0026lt;specSet\u0026gt; \u0026lt;propSet\u0026gt; \u0026lt;type\u0026gt;HostSystem\u0026lt;/type\u0026gt; \u0026lt;pathSet\u0026gt;configManager.advancedOption\u0026lt;/pathSet\u0026gt; \u0026lt;/propSet\u0026gt; \u0026lt;objectSet\u0026gt; \u0026lt;obj type=\u0026#34;HostSystem\u0026#34;\u0026gt;host-14\u0026lt;/obj\u0026gt; \u0026lt;skip\u0026gt;false\u0026lt;/skip\u0026gt; \u0026lt;/objectSet\u0026gt; \u0026lt;/specSet\u0026gt; \u0026lt;options\u0026gt;\u0026lt;/options\u0026gt; \u0026lt;/RetrievePropertiesEx\u0026gt; \u0026lt;/Body\u0026gt; \u0026lt;/Envelope\u0026gt; レスポンスで OptionManager オブジェクトの ID が返ってきます。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;soapenv:Envelope xmlns:soapenc=\u0026#34;http://schemas.xmlsoap.org/soap/encoding/\u0026#34; xmlns:soapenv=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34; xmlns:xsd=\u0026#34;http://www.w3.org/2001/XMLSchema\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34;\u0026gt; \u0026lt;soapenv:Body\u0026gt; \u0026lt;RetrievePropertiesExResponse xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;returnval\u0026gt; \u0026lt;objects\u0026gt; \u0026lt;obj type=\u0026#34;HostSystem\u0026#34;\u0026gt;host-14\u0026lt;/obj\u0026gt; \u0026lt;propSet\u0026gt; \u0026lt;name\u0026gt;configManager.advancedOption\u0026lt;/name\u0026gt; \u0026lt;val type=\u0026#34;OptionManager\u0026#34; xsi:type=\u0026#34;ManagedObjectReference\u0026#34;\u0026gt;EsxHostAdvSettings-14\u0026lt;/val\u0026gt; \u0026lt;/propSet\u0026gt; \u0026lt;/objects\u0026gt; \u0026lt;/returnval\u0026gt; \u0026lt;/RetrievePropertiesExResponse\u0026gt; \u0026lt;/soapenv:Body\u0026gt; \u0026lt;/soapenv:Envelope\u0026gt; この OptionManager オブジェクトの QueryOptions で、取得したい詳細オプションを指定します。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;Envelope xmlns=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34;\u0026gt; \u0026lt;Body\u0026gt; \u0026lt;QueryOptions xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;_this type=\u0026#34;OptionManager\u0026#34;\u0026gt;EsxHostAdvSettings-14\u0026lt;/_this\u0026gt; \u0026lt;name\u0026gt;VmOpNotificationToApp.Timeout\u0026lt;/name\u0026gt; \u0026lt;/QueryOptions\u0026gt; \u0026lt;/Body\u0026gt; \u0026lt;/Envelope\u0026gt; レスポンスで値が返ってきます。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;soapenv:Envelope xmlns:soapenc=\u0026#34;http://schemas.xmlsoap.org/soap/encoding/\u0026#34; xmlns:soapenv=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34; xmlns:xsd=\u0026#34;http://www.w3.org/2001/XMLSchema\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34;\u0026gt; \u0026lt;soapenv:Body\u0026gt; \u0026lt;QueryOptionsResponse xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;returnval\u0026gt; \u0026lt;key\u0026gt;VmOpNotificationToApp.Timeout\u0026lt;/key\u0026gt; \u0026lt;value xsi:type=\u0026#34;xsd:long\u0026#34;\u0026gt;300\u0026lt;/value\u0026gt; \u0026lt;/returnval\u0026gt; \u0026lt;/QueryOptionsResponse\u0026gt; \u0026lt;/soapenv:Body\u0026gt; \u0026lt;/soapenv:Envelope\u0026gt; 実例 (4)： ホストの詳細オプションの値を変更する 実例 (3) では、詳細オプションの確認に OptionManager オブジェクトの QueryOptions を使用しました。同じ OptionManager オブジェクトの UpdateOptions を使えば、設定を変更できます。ここでは、実例 (3) で調べた VmOpNotificationToApp.Timeout の値を変更したいものとします。\nOptionManager オブジェクトを取得するところまでは実例 (3) とまったく一緒で、あとは QueryOptions の代わりに UpdateOptions を使うだけです。\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;Envelope xmlns=\u0026#34;http://schemas.xmlsoap.org/soap/envelope/\u0026#34;\u0026gt; \u0026lt;Body\u0026gt; \u0026lt;UpdateOptions xmlns=\u0026#34;urn:vim25\u0026#34;\u0026gt; \u0026lt;_this type=\u0026#34;OptionManager\u0026#34;\u0026gt;EsxHostAdvSettings-14\u0026lt;/_this\u0026gt; \u0026lt;changedValue xmlns:XMLSchema-instance=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; XMLSchema-instance:type=\u0026#34;OptionValue\u0026#34;\u0026gt; \u0026lt;key\u0026gt;VmOpNotificationToApp.Timeout\u0026lt;/key\u0026gt; \u0026lt;value XMLSchema-instance:type=\u0026#34;xsd:long\u0026#34;\u0026gt;600\u0026lt;/value\u0026gt; \u0026lt;/changedValue\u0026gt; \u0026lt;/UpdateOptions\u0026gt; \u0026lt;/Body\u0026gt; \u0026lt;/Envelope\u0026gt; エラーなくレスポンスが返れば、作業は完了です。\n補足： WSDL の利用 vSphere Web Services API 用の WSDL も用意されており、次のいずれかの方法で入手できます。\nvCenter Server からインポートする https://\u0026lt;FQDN\u0026gt;/sdk/vimService?wsdl [vSphere Management SDK の一部としてダウンロード](http://vSphere Management SDK) する SoapUI など URL から WSDL をインポートできる SOAP クライアントを使うのであれば、vCenter Server からのインポートが便利です。リクエストボディも自動生成されるサンプルをベースにしてさくさく作れます。\nPowerShell でも、New-WebServiceProxy コマンドレットで WSDL を基にローカルプロキシを作成できます（ただし、作成に数分かかります）。完全にはテストしていませんが、がんばればこの手段でも操作は可能そうです。例えば、ContainerView を作るところまでの処理は以下の通りです。\n$WSProxy = New-WebServiceProxy -Uri https://kuro-vcs01.kurokobo.internal/sdk/vimService?wsdl -Namespace WSProxy -Class \u0026#34;vSWSAPI\u0026#34; $WSProxy.Url = \u0026#34;https://kuro-vcs01.kurokobo.internal/sdk/vimService\u0026#34; $WSProxy.CookieContainer = New-Object System.Net.CookieContainer $SI = New-Object -TypeName \u0026#34;WSProxy.ManagedObjectReference\u0026#34; -Property @{type=\u0026#34;ServiceInstance\u0026#34;; value=\u0026#34;ServiceInstance\u0026#34;} $SC = $WSProxy.RetrieveServiceContent($SI) $WSProxy.Login($SC.sessionManager, \u0026#34;wsapi@vsphere.local\u0026#34;,\u0026#34;VMware123!\u0026#34;, \u0026#34;en_US\u0026#34;) $WSProxy.CreateContainerView($SC.viewManager, $SC.rootFolder, \u0026#34;VirtualMachine\u0026#34;, $true) ... おわりに vSphere Web Services API を直接触る方法を紹介しました。日本語の情報がほぼないことから、そもそも需要がないということにもなりそうですが、かゆいところに好きなだけ手を伸ばせる手段でもあるため、知っておくと自力で歩ける範囲が少し広がります。\nPowerCLI や pyVmomi など使いやすいように整備されたツールはいくつもありますが、結局は API のラッパの類でしかなく、凝った処理のために API の仕様とツールの実装を探らざるを得なくなることもあります。そうしたとき、それらのツールの裏側の処理を想像できるようになっておくことは、何らかの助けにはなりそうです。\n","date":"2024-03-06T16:02:45Z","image":"/archives/4968/img/image-400.png","permalink":"/archives/4968/","title":"vSphere Web Services API（SOAP API）の触り方"},{"content":"はじめに AWX の Automation Mesh を構成する機能として、過去に紹介した Execution Node に加えて、Hop Node と Mesh Ingress が最近のリリースで追加されました。\n関連する情報は AWX のドキュメント や AWX Operator のドキュメント にもまとまっていますが、本エントリでは Mesh Ingress を中心にこれらの新機能を簡単に紹介します。\nExecution Node と Hop Node Automation Mesh を構成する基本のノードは、Execution Node と Hop Node の二種類です。Execution Node は AWX の 21.7.0 で、Hop Node は 23.0.0 でそれぞれ追加されました。\nExecution Node については 過去のエントリ でも触れましたが、まずは基本となるこの二種類を改めて簡単に紹介します。関連する公式のドキュメントは以下です。\n8. Managing Capacity With Instances — Ansible AWX community documentation 概要 Execution Node Execution Node は、Kubernetes クラスタの外に構成されたスタンドアロンのインスタンスで、AWX で起動されたジョブをクラスタ外のリモートホスト上で実行するために使用します。\nネットワークの都合で AWX からは直接到達できないターゲットノードに対しても、Execution Node を介してプレイブックを実行できます。\nExecution Node には、インストールバンドルにより、Receptor と Ansible Runner、Podman が導入されます。AWX でジョブを起動すると、Podman のコンテナとして Execution Environment（EE）が起動し、その中でプレイブックが実行されます。\nHop Node Hop Node は、AWX と Execution Node の間の通信を中継するためのインスタンスです。Execution Node と同様、Kubernetes クラスタの外にスタンドアロンで構成します。\nExecution Node が実装された当初は、Execution Node は AWX から直接到達できる範囲にしか配置できませんでした。Hop Node により、任意の数の Hop Node を AWX と Execution Node の間に配置して、ジョブをさらに遠くのネットワークまで届けられるようになりました。\nHop Node には、インストールバンドルにより Receptor のみが導入されます。Execution Node と異なり Ansible Runner と Podman は導入されず、ジョブの実行機能は持ちません。\n構成方法 例として、Execution Node と Hop Node を含んだ Automation Mesh を実際に構成します。\nトポロジの設計 まずは Automation Mesh のトポロジを設計します。今回は下図の構成で考えます。Execution Node をふたつ用意し、ひとつは AWX から直接、もうひとつは Hop Node を経由してつながるものとします。\nトポロジを設計する際は、ノード間の接続方向（図中の矢印）も設計が必要です。これは、Automation Mesh のバックエンド接続の方向で、ノードの間に ファイアウォールが配置されている場合の穴あけの方向 に相当します。\nただし、AWX からその隣接ノードへ は 必ずアウトバウンド方向 で設計します。AWX 以外のノード間の接続方向は任意 です。これを踏まえて、上図にホスト名とポート番号を加えて少しだけ細かくした図が以下です。\nホストの準備 RHEL ファミリのホストを用意して、ネットワークを設定します。経路上やホスト上でファイアウォールを利用している場合は、前述の接続方向にあわせて穴をあけておきます。\nインスタンスの追加 設計したすべてのノードを、AWX の Web UI からインスタンスとして追加します。\nAdministration \u0026gt; Instances \u0026gt; Add から、Create new Instance 画面を開きます。 次の通りに構成します。 Host Name として、そのノードに接続しにくる側（矢印の根元側）で名前解決可能なホスト名（または IP アドレス）を指定します。 Listener Port は、そのノードが 接続を受ける側（矢印の先端側）の場合 に入力します。標準は 27199 です。 Instance Type は、そのノードにあわせて Execution か Hop から選択します。 そのノードが AWX の隣接ノードの場合 は、Peers from control nodes にチェック を入れます。 Save をクリックします。 ノードの数だけこれを繰り返して、全ノードがインスタンスとして登録された状態にします。\nピアの構成 インスタンスを全て追加し終わったら、それぞれのインスタンスの ピア を指定します。つまり、インスタンス同士を設計通りに矢印でつなぐ作業 です。\nAdministration \u0026gt; Instances で、接続しにいく側（矢印の根元側）のインスタンスを選択し、Peers タブを開きます。 Associate をクリックし、接続を受ける側（矢印の先端側）のインスタンスにチェックを入れて Save をクリックします。 AWX から直接つながるノード（今回の例では exec01 と hop01）へのピア設定は、インスタンスの追加時に Peers from control nodes にチェックをいれたことで完了しています。したがって、今回の構成では hop01.ansible.internal の Peers タブで exec02.ansible.internal を追加すれば完了です。\n矢印の数だけこの作業を繰り返せば、Automation Mesh のトポロジが完成します。トポロジは、Administration \u0026gt; Topology View で確認できます。この画面でも、接続の方向は矢印で表現 されているため、正しく構成できていることを線のつながりと矢印の方向で確認します。\nインストールバンドルの実行 トポロジが設計通りに完成したら、それぞれのインスタンスの詳細ページからインストールバンドルをダウンロードし、inventory.yml の ansible_user を修正したうえでプレイブックを実行します。\n$ ansible-galaxy collection install -f -r requirements.yml ... $ ansible-playbook -i inventory.yml install_receptor.yml ... なお、インストールバンドルにはそのインスタンス専用の証明書やピアの情報が含まれているため、使いまわしはできません。全インスタンスについて個別にインストールバンドルをダウンロードして実行します。\nインストール後、しばらくして各インスタンスの Status が Ready になれば正常です。\nインスタンスグループの追加 インスタンスグループを作成して、Execution Node を追加します。\nAdministration \u0026gt; Instance Groups \u0026gt; Add \u0026gt; Add instance group をクリックします。 Name を入力して Save をクリックします。 Instances タブを開きます。 Associate をクリックし、任意の Execution Node にチェックを入れて Save をクリックします。 グループに複数の Execution Node が含まれる場合は、ジョブの実行にはいずれかのインスタンスが利用されます。厳密に特定の Execution Node のみでジョブを実行させたい場合は、インスタンスがひとつだけのグループを作成します。\nジョブの実行 作成したインスタンスグループをジョブテンプレートの Instance Groups で指定することで、ジョブが Execution Node で実行されるようになります。\nジョブを実行して、Execution Node 側の awx ユーザで Podman の状態を確認すると、Execution Environment が動作している様子が観察できます。\n[awx@exec01 ~]$ podman ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a02702fb0b21 quay.io/ansible/awx-ee:latest ansible-playbook ... 1 second ago Up 1 second ago ansible_runner_1 [awx@exec02 ~]$ podman ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 40ef61ed9c60 quay.io/ansible/awx-ee:latest ansible-playbook ... 1 second ago Up 1 second ago ansible_runner_2 Mesh Ingress AWX の 23.8.0 で、Automation Mesh を構成する要素として新しく Mesh Ingress が追加されました。関連ドキュメントは以下です。\n8. Managing Capacity With Instances — Ansible AWX community documentation Mesh Ingress - Ansible AWX Operator Documentation 概要 これまでの Execution Node と Hop Node を使った Automation Mesh では、AWX とそれに隣接するノードの接続は、前述の通り、AWX からみてアウトバウンド方向、つまり、Kubernetes クラスタから出ていく方向でしか構成できません でした。\nこれを解決するのが Mesh Ingress で、これは Kubernetes クラスタ外からのインバウンド方向の接続を受け入れる Hop Node の一種 です。Hop Node の一種ですが、Hop Node とは異なり Kubernetes クラスタ内の Pod として動作します。\nこれにより、ネットワークポリシの都合で Kubernetes クラスタからのアウトバウンド方向のピアリングが許容されない場合でも、Automation Mesh を構成できるようになりました。\n構成方法 MeshIngress を構成するには、AWX Operator を利用します（がんばれば手動でもできますがまったくおすすめしません）。2.11.0 以降で、新しい CR である AWXMeshIngress が定義できるようになっています。\nなお、CR の詳細や例、デプロイ方法などは AWX Operator のドキュメント にも記載があります。\nトポロジの設計 今回は、前述の図の通りの構成を目指します。Mesh Ingress が クラスタ外のノードからインバウンド方向で接続されている 点が重要です。\n技術的な実装は後述しますが、Mesh Ingress はバックエンドに（Executiton Node や Hop Node のような TCP ではなく）WebSocket を利用しています。上図をもう少し詳しくしたものが以下です。\n以降、Mesh Ingress の構成に絞って紹介します。Mesh Ingress 以外の Execution Node と Hop Node の構成方法は、前述した従来の手順と同じです。\nCR の作成 今回の構成に合わせた CR は以下の通りです。これは Traefik 用ですが、Nginx など他の Ingress Controller でも適切に CR をカスタマイズすれば動作します（ドキュメント にいくつか CR の例を 増やす予定 です）。\n--- apiVersion: awx.ansible.com/v1alpha1 kind: AWXMeshIngress metadata: namespace: awx name: inbound-hop01 spec: deployment_name: awx ingress_type: IngressRouteTCP ingress_controller: traefik ingress_class_name: traefik ingress_api_version: traefik.io/v1alpha1 external_hostname: inbound-hop01.ansible.internal deployment_name で Mesh Ingress を追加する AWX のインスタンス名を指定し、external_hostname でクラスタ外からの接続を受け入れるための FQDN を指定します。併せて、接続点となる Ingress（OpenShift の場合は Route）の構成のため、今回使う Traefik にあわせて ingress_type と ingress_api_version を指定しています。\nこれを作成すると、AWX Operator がいろいろして、デプロイが完了します。\n$ kubectl -n awx logs deployments/awx-operator-controller-manager ... ----- Ansible Task Status Event StdOut (awx.ansible.com/v1alpha1, Kind=AWXMeshIngress, inbound-hop01/awx) ----- PLAY RECAP ********************************************************************* localhost : ok=19 changed=3 unreachable=0 failed=0 skipped=5 rescued=0 ignored=0 $ kubectl -n awx get deployment,pod,service,ingressroutetcp.traefik.io NAME READY UP-TO-DATE AVAILABLE AGE ... deployment.apps/inbound-hop01 1/1 1 1 35s NAME READY STATUS RESTARTS AGE ... pod/inbound-hop01-b858d78cb-89ctn 1/1 Running 0 35s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ... service/inbound-hop01 ClusterIP 10.43.165.50 \u0026lt;none\u0026gt; 27199/TCP 36s NAME AGE ingressroutetcp.traefik.io/inbound-hop01 37s AWX Operator は、AWX へのインスタンスの追加と AWX からのピアの設定も実施するため、AWX の Web UI ではこの段階で Mesh Ingress のインスタンスが確認できます。名前は CR の name で指定したもので、Node Type は hop です。\nMesh Ingress は CR のデプロイだけで構成が完了 します。Web UI からの操作は不要で、インストールバンドルなども存在しません。したがって、以上で Mesh Ingress の構成は完了です。\n他のインスタンスの構成 Mesh Ingress 以外のノードを、前述した従来の手順に従って構成します。Mesh Ingress をピアに持つインスタンスも構成することになりますが、手順は他のインスタンスの場合と同じです。\nホストの準備 インスタンスの追加 ピアの構成 インストールバンドルの実行 インスタンスグループの追加 適切に構成できれば、Topology View でも Mesh Ingress（inbound-hop01）に矢印が集まっている（インバウンド方向で接続されている）ことが確認できます。\nジョブの実行 インスタンスグループを指定してジョブを起動すると、ジョブが Mesh Ingress を経由して Execution Node まで届けられて実行されたことが、Execution Node の Podman の状態から確認できます。\n[awx@exec03 ~]$ podman ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6b149d385677 quay.io/ansible/awx-ee:latest ansible-playbook ... 1 second ago Up 2 seconds ago ansible_runner_3 [awx@exec04 ~]$ podman ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 77c74eec136d quay.io/ansible/awx-ee:latest ansible-playbook ... 2 seconds ago Up 2 seconds ansible_runner_4 参考： 技術的な補足 Mesh Ingress 周辺の技術的ないろいろです。\nMesh Ingress の実装のモチベーション Mesh Ingress のない Automation Mesh では、AWX とそれに隣接するノードの接続は、AWX からみてアウトバウンド方向 でしか構成できません。これは、コントロールプレーンのレプリカ数が 2 以上のとき に、インバウンド方向のバックエンド接続を維持できなくなる ことが理由です。\nReceptor は、複数のノードを仮想的にひとつのノードとして扱うような冗長化の機能を持ちません。すべてのノードは、それぞれが独立した別のノードとしてメッシュに参加します。\nここで、コントロールプレーンのレプリカ数が 2 以上の場合、すなわち、Task の Deployment の replicas が 2 以上の場合を考えます。Deployment を外部に公開する場合、一般的には図のように Service を構成し、外部からの通信がいずれかの Pod へロードバランスされる状態 にします。\nここで、仕様上、==Receptor としては二つの Pod 内の Receptor がそれぞれ別のノードとして扱われる== 点に注意します。Receptor としては上図は 3 ノードのメッシュであり、インバウンド方向でピア接続を確立する には、クラスタ外のノードはこの二つの Pod と明示的かつ別々に通信 できなければなりません。つまり、ロードバランスされると通信の到達先が意図せずに切り替わって困る ことになります。\nしたがって、クラスタ外からのピア接続を受け入れるには、Receptor としては Pod と Service が一対一で対応していることが必須 で、通常の Web アプリケーションなどではたいへん便利な Kubernetes のレプリカ機能も、Receptor とはすこぶる相性が悪いということになります。\nこうした背景から、Pod と Service が必ず一対一になるようレプリカ数を 1 に固定した Deployment を作って、それにクラスタ外からの接続を引き受けさせようとして実装されたのが Mesh Ingress です。\nMesh Ingress の仕組み Mesh Ingress の内部実装を少し掘り下げて紹介します。\nKubernetes 上に作成されるリソース Mesh Ingress を Kubernetes 上にデプロイすると、AWX Operator によって次のリソースが作成されます。\nReceptor が動作する Deployment（Pod） Pod のポートをクラスタ内に公開する Service（type: ClusterIP） Service を外部に公開する Ingress（または Route） Deployment を起動する Service Account Receptor の設定ファイルを含む ConfigMap $ kubectl -n awx get deployment,pod,service,ingressroutetcp.traefik.io,serviceaccount,configmap NAME READY UP-TO-DATE AVAILABLE AGE ... deployment.apps/inbound-hop01 1/1 1 1 35s NAME READY STATUS RESTARTS AGE ... pod/inbound-hop01-b858d78cb-89ctn 1/1 Running 0 35s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ... service/inbound-hop01 ClusterIP 10.43.165.50 \u0026lt;none\u0026gt; 27199/TCP 36s NAME AGE ingressroutetcp.traefik.io/inbound-hop01 37s NAME SECRETS AGE ... serviceaccount/inbound-hop01 0 37s NAME DATA AGE ... configmap/inbound-hop01-receptor-config 1 36s Pod で利用されるイメージはデフォルトで quay.io/ansible/awx-ee:latest です。AWX 本体の CR に control_plane_ee_image の指定があればそれが利用 されます。\nReceptor の設定 Mesh Ingress 用の Receptor の設定ファイルは次の通りです。ConfigMap か Pod 内の /etc/receptor/receptor.conf で確認できます。\n--- - node: id: inbound-hop01 - log-level: debug - control-service: service: control - ws-listener: port: 27199 tls: tlsserver - tls-server: cert: /etc/receptor/tls/receptor.crt key: /etc/receptor/tls/receptor.key name: tlsserver clientcas: /etc/receptor/tls/ca/mesh-CA.crt requireclientcert: true mintls13: false Listener として ws-listener が定義されており、WebSocket をポート 27199 で待ち受けている ことがわかります。ここには tls が指定されているため、実際には WebSocket over TLS（WSS）です。\nReceptor 用の証明書 前述の tls-server の定義の通り、隣接ノードとの接続時には相互に証明書の検証が行われるように設定されています。\nMesh Ingress は、AWX 本体が利用している Receptor の CA 証明書と秘密鍵（Kubernetes 上の Secret リソース）をそのままマウントしています。また、それを使って 自分自身の起動時に自分自身用のサーバ証明書を生成 しています。\nこのサーバ証明書の有効期限は一年間と比較的短いですが、Pod が再作成されるたびに証明書も再生成されるため、同一の Pod が一年以上動作し続けない限りは失効しません（念のため 延長する是非を伺い中 です）。\n証明書のマウントっぷりや証明書を生成するコマンドは、Deployment の定義から確認できます。\n$ kubectl -n awx get deployment/inbound-hop01 -o yaml apiVersion: apps/v1 kind: Deployment metadata: ... name: inbound-hop01 ... spec: ... template: ... spec: containers: - args: - /bin/sh - -c - | internal_hostname=inbound-hop01 external_hostname=inbound-hop01.ansible.internal receptor --cert-makereq bits=2048 \\ commonname=$internal_hostname \\ dnsname=$internal_hostname \\ nodeid=$internal_hostname \\ dnsname=$external_hostname \\ outreq=/etc/receptor/tls/receptor.req \\ outkey=/etc/receptor/tls/receptor.key receptor --cert-signreq \\ req=/etc/receptor/tls/receptor.req \\ cacert=/etc/receptor/tls/ca/mesh-CA.crt \\ cakey=/etc/receptor/tls/ca/mesh-CA.key \\ outcert=/etc/receptor/tls/receptor.crt \\ verify=yes exec receptor --config /etc/receptor/receptor.conf image: quay.io/ansible/awx-ee:latest ... name: inbound-hop01-mesh-ingress ... volumeMounts: - mountPath: /etc/receptor/receptor.conf name: inbound-hop01-receptor-config subPath: receptor.conf - mountPath: /etc/receptor/tls/ca/mesh-CA.crt name: inbound-hop01-receptor-ca readOnly: true subPath: tls.crt - mountPath: /etc/receptor/tls/ca/mesh-CA.key name: inbound-hop01-receptor-ca readOnly: true subPath: tls.key - mountPath: /etc/receptor/tls/ name: inbound-hop01-receptor-tls ... volumes: - emptyDir: {} name: inbound-hop01-receptor-tls - name: inbound-hop01-receptor-ca secret: defaultMode: 420 secretName: awx-receptor-ca - configMap: defaultMode: 420 items: - key: receptor_conf path: receptor.conf name: inbound-hop01-receptor-config name: inbound-hop01-receptor-config ... args で指定されているコマンドの通り、Mesh Ingress が利用するサーバ証明書は、CN だけでなく SAN も使って 内部用と外部用の二つのホスト名の正当性を保証 するように作られます。今回の例では、内部用が inbound-hop01 で、外部用が FQDN の inbound-hop01.ansible.internal です。\n実際、証明書の内容を確認するとそのようになっています。\n$ kubectl -n awx exec -it deployment/inbound-hop01 -- openssl x509 -text -in /etc/receptor/tls/receptor.crt -noout Certificate: Data: ... Issuer: CN = awx Receptor Root CA Validity Not Before: Feb 16 13:00:59 2024 GMT Not After : Feb 16 13:00:59 2025 GMT Subject: CN = inbound-hop01 ... X509v3 extensions: ... X509v3 Subject Alternative Name: DNS:inbound-hop01, DNS:inbound-hop01.ansible.internal, othername: 1.3.6.1.4.1.2312.19.1::inbound-hop01 ... ホスト名が二種類用意されているのは、Mesh Ingress に対して隣接ノードが接続する際に 二種類のホスト名が使われる からです。\n隣接ノードとの接続 Mesh Ingress は、クラスタ内で AWX からの接続を受け付けるだけでなく、クラスタ外の他のノードからの接続も受け付けます。このとき、クラスタ外の他のノードは Ingress（または Route）を宛先に接続するしかありませんが、==AWX は同じクラスタ内にいる== ため Service を宛先にして接続 しています。\nこのため、AWX が Mesh Ingress に接続するときは Service の名称 が、クラスタ外のノードが接続するときは FQDN が利用されます。したがって、Mesh Ingress はこの二種類のホスト名の正当性をどちらも証明できる必要があり、これが証明書に二種類のホスト名が含まれていた背景です。\nこの二つのホスト名やポート番号の実際の値は、Mesh Ingress のインスタンスの Listener Addresses タブで確認できます。\nMesh Ingress 用の Ingress リソースを自製するには Ingress Controller の都合や環境の制約など、何らかの理由で、AWX Operator で作成できる Ingress では不十分な場合は、Ingress のみ自製すれば対応できます。CR で ingress_type を none（デフォルト）にすると、AWX Operator は Ingress をデプロイしません。なお、external_hostname は、Receptor の証明書の生成にも使用されるため、依然として指定は必要です。\n--- apiVersion: awx.ansible.com/v1alpha1 kind: AWXMeshIngress metadata: name: inbound-hop01 spec: deployment_name: \u0026lt;awx instance name\u0026gt; ingress_type: none external_hostname: inbound-hop01.ansible.internal 自製する Ingress の要件は大まかには次の通りです。type: ClusterIP の Service が Operator により作成されるので、これをバックエンドに指定します。\nWebSocket に対応していること 443 番ポートでアクセスできること TLS パススルーを有効にすること（TLS の終端は Receptor が行うため） Mesh Ingress 用の Service の 27199 番ポートにルーティングすること AWXMeshIngress の external_hostname と同じホスト名を使っていること ドキュメント に例を載せる 予定 です。\nMesh Ingress を削除するには CR AWXMeshIngress を kubectl delete で削除するだけです。\nCR の Finalizer が AWX Operator により起動し、Kubernetes 上のリソースの削除だけでなく、AWX からのインスタンスの削除も実行されます。\n各ノードを冗長化するには 前述の理由から、Mesh Ingress のレプリカ数は 1 で固定で増やせません。\nこのため、Mesh Ingress 自体を冗長化したい場合は、別の Mesh Ingress を追加でデプロイして Automation Mesh に参加させることで対応します。\nExecution Node や Hop Node を冗長化させたい場合も、隣にもう一台同じ役割のものを追加で用意して Automation Mesh に参加させるだけです。Execution Node は、冗長化させたい複数台を同じインスタンスグループに含めることで、一方で障害が発生しても他方で実行される状態を保てます。\nおわりに AWX で Automation Mesh を構成するための Execution Node、Hop Node、Mesh Ingress のそれぞれを紹介しました。これまで以上に柔軟な構成が取れるようになり、AWX が使える範囲も広がりそうです。\n趣味の一環で Mesh Ingress のアーキテクチャの検討 には初期のころから参加していましたが、出した案がわりと素直に採用されておもしろかったです。AWX Operator 側の実装も、Ingress 周辺を少しだけ担当しています。Nginx と Traefik 以外の Ingress Controller はまったくテストできていないので、不具合があったら Issue を作って教えてください。\n","date":"2024-02-19T05:12:48Z","image":"/archives/5141/img/image-393.png","permalink":"/archives/5141/","title":"AWX の Automation Mesh で Hop Node と Mesh Ingress を使う"},{"content":"はじめに Ansible でよくある困りごとのひとつとして、タスクのログに複数行の文字列が含まれるとき、以下のように人間の眼にはやさしくない表示になることが挙げられます。改行がエスケープシーケンスとして表示されるためです。\n... TASK [Referring undefined variable] ******************************************** fatal: [localhost]: FAILED! =\u0026gt; {\u0026#34;msg\u0026#34;: \u0026#34;The task includes an option with an unde fined variable. The error was: \u0026#39;undefined_variable\u0026#39; is undefined. \u0026#39;undefined_var iable\u0026#39; is undefined\\n\\nThe error appears to be in \u0026#39;/runner/project/demo.yaml\u0026#39;: l ine 20, column 7, but may\\nbe elsewhere in the file depending on the exact synta x problem.\\n\\nThe offending line appears to be:\\n\\n\\n - name: Referring undef ined variable\\n ^ here\\n\u0026#34;} ... この対策として、改行が改行として表示されるように 出力を YAML 形式にする 方法がよく知られています。本エントリ公開時点では、次の二つの方法が一般的です。\nAnsible の組み込みの機能を利用する（Ansible 2.13 以降のみ） ansible.cfg の callback_result_format（または環境変数 ANSIBLE_CALLBACK_RESULT_FORMAT）に yaml を指定する コレクション community.genral のコールバックプラグイン yaml を利用する ansible.cfg の stdout_callback（または環境変数 ANSIBLE_STDOUT_CALLBACK）に community.general.yaml を指定する 一方で、同じ困りごとは AWX でも発生しますが、AWX で同じ対策を試みても意外と素直にいきません。\n先日、これに関する質問が Ansible Community Forum に寄せられ ました。フォーラムでもぼくから回答済みですが、本エントリでは、AWX でジョブのログを YAML 形式で出力させる方法を改めて日本語で簡単に紹介します。\n基本的な考え方 うまく構成できれば 前述のどちらの対策も AWX で利用できます が、いずれの場合でも次の点が重要です。\n利用する EE のイメージ が YAML 形式をサポートできるように正しく構成されていること 場合によっては AWX のコントロールプレーンの EE も変更が必要なこと ジョブ用の 環境変数または ansible.cfg を正しく構成する こと 手順 以下、前述の点を順に紹介します。\n(1) 利用する EE イメージを準備する AWX では、すべてのジョブは例外なく EE 内で処理されます。したがって、そもそもの EE のイメージが YAML 形式での出力に対応している必要があります。\n技術的な背景は後述しますが、実施する対策ごとの要件は次の通りです。\ncallback_result_format を変更したい場合 Ansible 2.13 以降が導入されていること Ansible Runner に含まれるコールバックプラグイン awx_display がオプション callback_result_format を扱えること stdout_callback を変更したい場合 コレクション community.general がインストールされていること Ansible Builder を使ってていねいにビルドしてももちろんよいですが、次のような Dockerfile をビルドすれば、既成の EE イメージを簡単に修正できます。ここでは quay.io/ansible/awx-ee:latest をベースに必要な変更を加えています。\nFROM quay.io/ansible/awx-ee:latest # 必須 USER root # callback_result_format を変更したい場合 ## プラグイン awx_display が callback_result_format を扱えるようにソースコードを修正する RUN sed -i \u0026#39;s/ - default_callback/ - default_callback\\n - result_format_callback/g\u0026#39; /usr/local/lib/python3.9/site-packages/ansible_runner/display_callback/callback/awx_display.py # stdout_callback を変更したい場合 ## コレクション community.general をインストールする RUN ansible-galaxy collection install -p /usr/share/ansible/collections/ansible_collections community.general # 必須 USER 1000 ビルドしたら、コンテナレジストリにプッシュします。\n(2) コントロールプレーンの EE を変更する AWX のコントロールプレーン（*-task の Pod）では、プロジェクトの同期などのジョブを実行する EE として *-ee コンテナが動作しています。このイメージにはデフォルトで quay.io/ansible/awx-ee:latest が使われますが、以下の場合にはこの変更も必要です。\nプロジェクトの同期のログも YAML 形式にしたい場合 YAML 形式にする設定を AWX の Extra Environment Variables で行う場合（後述） とくに、community.general.yaml を利用したい場合は、コントロールプレーンの EE を変更しないとプロジェクトの同期がエラーで失敗するようになるため注意が必要です。\nコントロールプレーンの EE イメージを変更するには、AWX Operator でデプロイするカスタムリソース AWX の spec に control_plane_ee_image を追加します。\n... spec: ... control_plane_ee_image: registry.example.com/ansible/awx-ee:latest-yaml ... (3) 目的のオプションを変更する 最後に、実際に callback_result_format または stdout_callback を変更します。なお、callback_result_format は環境変数と ansible.cfg のどちらでも変更できます が、stdout_callback は環境変数でしか変更できません。\n環境変数は Settings \u0026gt; Job settings の Extra Environment Variables で指定できますが、グローバルに変更される点に注意が必要です。細かく制御したい場合はコンテナグループを作って Pod に env を持たせるほうがよいでしょう。\n{ # callback_result_format を変更したい場合 \u0026#34;ANSIBLE_CALLBACK_RESULT_FORMAT\u0026#34;: \u0026#34;yaml\u0026#34; # stdout_callback を変更したい場合 \u0026#34;ANSIBLE_STDOUT_CALLBACK\u0026#34;: \u0026#34;community.general.yaml\u0026#34; } callback_result_format は、プロジェクトのルートに次の ansible.cfg を配置することでも変更できます。\n[defaults] callback_result_format = yaml 結果 AWX でもログが YAML 形式で表示されるようになり、読みやすくなります。\n... TASK [Referring undefined variable] ******************************************** fatal: [localhost]: FAILED! =\u0026gt; msg: |- The task includes an option with an undefined variable. The error was: \u0026#39;undefined_variable\u0026#39; is undefined. \u0026#39;undefined_variable\u0026#39; is undefined The error appears to be in \u0026#39;/runner/project/demo.yaml\u0026#39;: line 20, column 7, but may be elsewhere in the file depending on the exact syntax problem. The offending line appears to be: - name: Referring undefined variable ^ here ... 技術的な背景 細かい余談です。\ncallback_result_format のこと callback_result_format は、Ansible 2.13 以降でコールバックプラグイン default に組み込まれたオプションです。AWX のデフォルトの EE イメージには Ansible 2.15 が導入されているため、素直に考えればこのオプションも使えそうに思えますが、実際には（少なくとも本エントリ公開時点の実装では）前述のようにソースコードをいじらなければ使えません。これを理解するには、以下の点が重要です。\nAWX のジョブは、実際には Ansible Runner 経由で実行されること Ansible Runner でプレイブックを実行すると、stdout コールバックプラグインが強制的に awx_display に変更されること 本エントリ公開時点の awx_display（Ansible Runner 2.3.4 に同梱のもの）は、オプション callback_result_format に対応していないこと 実際、オプション callback_result_format に yaml を指定した状態で次のタスクを AWX で実行して設定を確認すると、callback_result_format はあくまでコールバックプラグイン default の設定を変更するだけで、awx_display に対しては何ら影響を与えられていないことがわかります。\n- name: Dump stdout configuration ansible.builtin.debug: var: configuration vars: configuration: 01_stdout_callback__________: \u0026#34;{{ lookup(\u0026#39;config\u0026#39;, \u0026#39;DEFAULT_STDOUT_CALLBACK\u0026#39;, show_origin=true, errors=\u0026#39;ignore\u0026#39;) }}\u0026#34; 02_result_format_default____: \u0026#34;{{ lookup(\u0026#39;config\u0026#39;, \u0026#39;result_format\u0026#39;, plugin_type=\u0026#39;callback\u0026#39;, plugin_name=\u0026#39;default\u0026#39;, show_origin=true, errors=\u0026#39;ignore\u0026#39;) }}\u0026#34; 03_result_format_awx_display: \u0026#34;{{ lookup(\u0026#39;config\u0026#39;, \u0026#39;result_format\u0026#39;, plugin_type=\u0026#39;callback\u0026#39;, plugin_name=\u0026#39;awx_display\u0026#39;, show_origin=true, errors=\u0026#39;ignore\u0026#39;) }}\u0026#34; TASK [Dump stdout configuration] *********************************************** ok: [localhost] =\u0026gt; { \u0026#34;configuration\u0026#34;: { \u0026#34;01_stdout_callback__________\u0026#34;: \u0026#34;awx_display\u0026#34; \u0026#34;02_result_format_default____\u0026#34;: \u0026#34;yaml\u0026#34;, \u0026#34;03_result_format_awx_display\u0026#34;: \u0026#34;\u0026#34;, } } プラグインごとの有効なオプションは、そのプラグインの DOCUMENTATION の定義で決定されます。awx_display の実装ではフラグメント default_callback の取り込みが指定されている ため、一見、コールバックプラグイン default のオプションである callback_result_format も読み込まれそうです。\nDOCUMENTATION = \u0026#39;\u0026#39;\u0026#39; callback: awx_display short_description: Playbook event dispatcher for ansible-runner version_added: \u0026#34;2.0\u0026#34; description: - This callback is necessary for ansible-runner to work type: stdout extends_documentation_fragment: - default_callback requirements: - Set as stdout in config \u0026#39;\u0026#39;\u0026#39; しかしながら、Ansible 側の実装では、オプション callback_result_format の定義は default_callback ではなく別のフラグメントである result_format_callback に含まれます。\nしたがって、本エントリで紹介した前述の手順では、sed により extends_documentation_fragment に result_format_callback を追加しています（参考： ansible/ansible-runner#994）。\nDOCUMENTATION = \u0026#39;\u0026#39;\u0026#39; callback: awx_display short_description: Playbook event dispatcher for ansible-runner version_added: \u0026#34;2.0\u0026#34; description: - This callback is necessary for ansible-runner to work type: stdout extends_documentation_fragment: - default_callback - result_format_callback requirements: - Set as stdout in config \u0026#39;\u0026#39;\u0026#39; これにより、awx_display のオプションとして callback_result_format が指定できるようになります。かつ、awx_display の 実体 はデフォルトで default（後述）なので、実際にオプションの通りに出力形式が変更されることになります。\nTASK [Dump stdout configuration] *********************************************** ok: [localhost] =\u0026gt; configuration: 01_stdout_callback__________: awx_display 02_result_format_default____: yaml 03_result_format_awx_display: yaml stdout_callback のこと Ansible Runner は stdout コールバックプラグインとして強制的に awx_display を利用しますが、awx_display 自体は もともと指定されていたコールバックプラグインを継承 する形で読み込まれるようになっているため、もともとの指定が無視されるわけではありません。\n実装として、Ansible Runner は、環境変数 ANSIBLE_STDOUT_CALLBACK を強制的に awx_display に変更する 前 に、もともとの値を ORIGINAL_STDOUT_CALLBACK に保存 しています（ただし、ansible.cfg の stdout_callback ではなく環境変数 ANSIBLE_STDOUT_CALLBACK しか見ていない点は注意が必要です）。\nif self.env.get(\u0026#39;ANSIBLE_STDOUT_CALLBACK\u0026#39;): self.env[\u0026#39;ORIGINAL_STDOUT_CALLBACK\u0026#39;] = self.env.get(\u0026#39;ANSIBLE_STDOUT_CALLBACK\u0026#39;) self.env[\u0026#39;ANSIBLE_STDOUT_CALLBACK\u0026#39;] = \u0026#39;awx_display\u0026#39; さらに awx_display は、環境変数 ORIGINAL_STDOUT_CALLBACK にしたがって、継承するベースクラスを動的に変更 しています。デフォルトは default です。\n# Dynamically construct base classes for our callback module, to support custom stdout callbacks. if os.getenv(\u0026#39;ORIGINAL_STDOUT_CALLBACK\u0026#39;): default_stdout_callback = os.getenv(\u0026#39;ORIGINAL_STDOUT_CALLBACK\u0026#39;) elif IS_ADHOC: default_stdout_callback = \u0026#39;minimal\u0026#39; else: default_stdout_callback = \u0026#39;default\u0026#39; DefaultCallbackModule = callback_loader.get(default_stdout_callback).__class__ class CallbackModule(DefaultCallbackModule): \u0026#39;\u0026#39;\u0026#39; Callback module for logging ansible/ansible-playbook events. \u0026#39;\u0026#39;\u0026#39; CALLBACK_NAME = \u0026#39;awx_display\u0026#39; これにより、ユーザは環境変数 ANSIBLE_STDOUT_CALLBACK を指定すれば awx_display の実体を変更できる ことになります。\nただし、callback_result_format の項目で紹介した通り、awx_display にはコールバックプラグイン default のオプションの一部しか実装されていないため、独自のオプションを持つコールバックプラグインを指定するとエラーになる 場合があります。強引にどうにかしたい場合は、callback_result_format を無理やり有効化したように、awx_display がそのオプションを持てるように修正が必要です。\nおわりに なんというか、ややこしいので、AWX で簡単に設定できるようにしたいですね。まずは Ansible Runner 側に手を入れないとですが。\n","date":"2023-12-29T13:29:52Z","image":"/archives/5096/img/image-378.png","permalink":"/archives/5096/","title":"AWX のジョブのログを YAML 形式で表示させる"},{"content":"はじめに EDA Server のデプロイ方法として、公式のデプロイメントガイド では Docker Compose と Minikube が案内されています。\nその一方で、ひっそりと EDA Server Operator が公開されています。これは、EDA Server をデプロイするための Operator です。\nansible/eda-server-operator まだ開発中であり、前述のデプロイメントガイドに記載もないことから、完全にサポートされている手段とはまったく言えませんが、本エントリではこれを用いた EDA Server のデプロイと、その後の AWX との連携を、Kubernetes 上での EDA Server の動きを含めて簡単に紹介します。\nなお、この Operator の名称は EDA Controller Operator が正しいような気もしており、中の方々の意見を聞く意味も兼ねて Issue と PR を作っていますが、今のところ何ら確証はないので、本エントリでは現時点のドキュメントに従って EDA Server Operator と表記しています。\n本エントリ中で利用しているファイルは、GitHub のリポジトリ にも配置しています。\n追記（2023/08/12） 追記 (1) 公式のデプロイメントガイド が更新され、推奨されるデプロイ方式が Operator になりました。\n追記 (2) 当初、一貫してツール名称を EDA Controller と表記していましたが、アップストリーム版は EDA Server であり、EDA Controller は Ansible Automation Platform に含まれる製品版の名称 とのこと なので、タイトルと本文を修正しました。\nOperator の名称も EDA Server Operator で 正しい ようです。\nOperator を利用した EDA Server のデプロイ EDA Server Operator により、EDA Server のインスタンスが Kubernetes の Custom Resources（CR）として管理できるようになります。\n手順の全体的な考え方は、AWX を AWX Operator でデプロイするとき とほとんど一緒です。\nEDA Server Operator のデプロイ まずは Operator と Custom Resource Definitions（CRD）をデプロイして、CR がデプロイできる状態を整えます。今回は、Operator のドキュメント も参考にして、次の kustomization.yaml で EDA Server Operator の 0.0.5 をデプロイします。実際のファイルは GitHub のリポジトリ に配置しています。\n--- apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization namespace: eda generatorOptions: disableNameSuffixHash: true secretGenerator: - name: redhat-operators-pull-secret literals: - operator=eda resources: - github.com/ansible/eda-server-operator/config/default?ref=0.0.5 images: - name: quay.io/ansible/eda-server-operator newTag: 0.0.5 途中、redhat-operators-pull-secret を同時に生成させていますが、これは Operator の Deployment にデフォルトでこの名前の Secret が imagePullSecrets に指定されていて、存在しないと kubelet がひっそりとエラーを吐くためです。無視できるエラーですし、この後の CR のデプロイ中に同じ名前の Secret を作成する処理があるため、何もしなくてもエラーは解消されますが、そもそもエラーを吐かせない方がきれいなのでこうしています。\nこの kustomization.yaml を適用すると、CRD が追加され、Operator が動作し始めます。Namespace は eda です。\n$ kubectl -n eda get all NAME READY STATUS RESTARTS AGE pod/eda-server-operator-controller-manager-7bf7578d44-7r87w 2/2 Running 0 12s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/eda-server-operator-controller-manager-metrics-service ClusterIP 10.43.3.124 \u0026lt;none\u0026gt; 8443/TCP 12s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/eda-server-operator-controller-manager 1/1 1 1 12s NAME DESIRED CURRENT READY AGE replicaset.apps/eda-server-operator-controller-manager-7bf7578d44 1 1 1 12s EDA Server のデプロイ Operator が動作し始めたら EDA Server をデプロイできますが、下準備として、EDA Server の Web UI と API を HTTPS 化するため Ingress に使わせる自己署名証明書を作っておきます。\nEDA_HOST=\u0026#34;eda.example.com\u0026#34; openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -out tls.crt -keyout tls.key -subj \u0026#34;/CN=${EDA_HOST}/O=${EDA_HOST}\u0026#34; -addext \u0026#34;subjectAltName = DNS:${EDA_HOST}\u0026#34; また、EDA Server の管理者の初期パスワード、一緒にデプロイされる PostgreSQL のパスワードなどを Secret で明示的に指定するため、kustomization.yaml も用意します（Secret を渡さない場合は、デフォルトでランダム文字列で生成されます）。一緒に先の自己署名証明書も Secret にさせます。\n--- apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization namespace: eda generatorOptions: disableNameSuffixHash: true secretGenerator: - name: eda-secret-tls type: kubernetes.io/tls files: - tls.crt - tls.key - name: eda-database-configuration type: Opaque literals: - host=eda-postgres-13 - port=5432 - database=eda - username=eda - password=Ansible123! - type=managed - name: eda-admin-password type: Opaque literals: - password=Ansible123! resources: - eda.yaml 最後に、この中で指定している eda.yaml として、EDA Server のインスタンスを示す CR である EDA を定義します。この CR の spec で EDA Server の構成を変更できます。今回は以下の内容です。実際のファイルは GitHub のリポジトリ に配置しています。\n--- apiVersion: eda.ansible.com/v1alpha1 kind: EDA metadata: name: eda spec: admin_user: admin admin_password_secret: eda-admin-password ingress_type: ingress ingress_tls_secret: eda-secret-tls hostname: eda.example.com automation_server_url: https://awx.example.com/ automation_server_ssl_verify: no image: quay.io/ansible/eda-server image_version: sha-a6e4d66 api: replicas: 1 resource_requirements: requests: {} ui: replicas: 1 resource_requirements: requests: {} worker: replicas: 2 resource_requirements: requests: {} redis: replicas: 1 resource_requirements: requests: {} database: database_secret: eda-database-configuration storage_requirements: requests: storage: 8Gi resource_requirements: requests: {} 各設定の詳しい内容は Operator のドキュメント に（ある程度は）記載がありますが、記載がないものはソースコードを読んで判断が必要です（バージョンも 0.0.5 ですし、ドキュメントの不足は仕方がないですね）。\nadmin_user: admin admin_password_secret: eda-admin-password 初期の管理者のユーザ名とパスワードを指定しています。\ningress_type: ingress ingress_tls_secret: eda-secret-tls hostname: eda.example.com Web UI と API のエンドポイントを Ingress にするための設定です。HTTPS 化するために証明書も渡しています。\nautomation_server_url: https://awx.example.com/ automation_server_ssl_verify: no AWX の情報を与える部分です。automation_server_url には、AWX の URL を指定します。手元の AWX は自己署名証明書を使っているため、automation_server_ssl_verify は no です（ややこしいですが、true / false の Bool 値ではなく文字列としての yes / no で指定します）。\nimage: quay.io/ansible/eda-server image_version: sha-a6e4d66 EDA Server の Pod のイメージとして、デフォルトでは quay.io/ansible/eda-server:main が使われますが、最近の変更（Worker のクラス名の変更）に 0.0.5 の Operator では対応しておらず起動できない（Issue、PR）ため、少し古いイメージを明示しています。\napi: ... ui: ... worker: replicas: 2 ... redis: ... database: ... 各コンポーネントのレプリカ数やリソース構成を設定する部分です。最小構成を目指して組んでいますが、worker の replicas のみ 2 にしています。後述しますが、実装上、EDA Server は Worker の数で様々な処理の並列度が縛られるためです（将来的に default_worker と activation_worker、さらに scheduler に分かれて指定することになるはずですが、その場合も特に *_worker のレプリカ数は工夫が必要です）。\nなお、EDA Server と一緒にデプロイされる PostgreSQL の PV に任意の Storage Class を使わせたい要件があっても、そのための設定である database.postgres_storage_class が 0.0.5 では機能しません（Issue、PR）。したがって、クラスタのデフォルト（K3s の場合は local-path）が使われます。\nこのほか、EDA Server の環境変数をいじる extra_settings も 0.0.5 では機能しない（Issue、PR）ので、利用したい場合は注意が必要です。\nあとはこれを apply して待つと完了です。進捗は Operator のログで確認できます。\n$ kubectl apply -k . ... $ kubectl -n eda logs -f deployment/eda-server-operator-controller-manager ... ----- Ansible Task Status Event StdOut (eda.ansible.com/v1alpha1, Kind=EDA, eda/eda) ----- PLAY RECAP ********************************************************************* localhost : ok=54 changed=0 unreachable=0 failed=0 skipped=16 rescued=0 ignored=0 $ kubectl -n eda get eda,all,ingress,configmap,secret NAME AGE eda.eda.ansible.com/eda 3m50s NAME READY STATUS RESTARTS AGE pod/eda-server-operator-controller-manager-7bf7578d44-2wm69 2/2 Running 0 6m29s pod/eda-redis-7d78cdf7d5-z87kk 1/1 Running 0 3m34s pod/eda-postgres-13-0 1/1 Running 0 3m25s pod/eda-ui-647b989ccb-stqkp 1/1 Running 0 2m36s pod/eda-worker-fd594c44-96d9p 1/1 Running 0 2m32s pod/eda-worker-fd594c44-7jtcq 1/1 Running 0 2m32s pod/eda-api-5c467d6c48-88m8z 2/2 Running 0 2m39s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/eda-server-operator-controller-manager-metrics-service ClusterIP 10.43.133.61 \u0026lt;none\u0026gt; 8443/TCP 6m29s service/eda-redis-svc ClusterIP 10.43.144.67 \u0026lt;none\u0026gt; 6379/TCP 3m36s service/eda-postgres-13 ClusterIP None \u0026lt;none\u0026gt; 5432/TCP 3m27s service/eda-api ClusterIP 10.43.89.128 \u0026lt;none\u0026gt; 8000/TCP 2m41s service/eda-daphne ClusterIP 10.43.12.68 \u0026lt;none\u0026gt; 8001/TCP 2m41s service/eda-ui ClusterIP 10.43.136.60 \u0026lt;none\u0026gt; 80/TCP 2m38s service/eda-worker ClusterIP 10.43.201.230 \u0026lt;none\u0026gt; 8080/TCP 2m33s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/eda-server-operator-controller-manager 1/1 1 1 6m29s deployment.apps/eda-redis 1/1 1 1 3m34s deployment.apps/eda-ui 1/1 1 1 2m36s deployment.apps/eda-worker 2/2 2 2 2m32s deployment.apps/eda-api 1/1 1 1 2m39s NAME DESIRED CURRENT READY AGE replicaset.apps/eda-server-operator-controller-manager-7bf7578d44 1 1 1 6m29s replicaset.apps/eda-redis-7d78cdf7d5 1 1 1 3m34s replicaset.apps/eda-ui-647b989ccb 1 1 1 2m36s replicaset.apps/eda-worker-fd594c44 2 2 2 2m32s replicaset.apps/eda-api-5c467d6c48 1 1 1 2m39s NAME READY AGE statefulset.apps/eda-postgres-13 1/1 3m25s NAME CLASS HOSTS ADDRESS PORTS AGE ingress.networking.k8s.io/eda-ingress traefik eda.example.com 192.168.0.219 80, 443 2m35s NAME DATA AGE configmap/kube-root-ca.crt 1 6m29s configmap/eda-eda-configmap 2 2m43s configmap/eda-server-operator 0 6m28s NAME TYPE DATA AGE secret/redhat-operators-pull-secret Opaque 1 6m29s secret/eda-admin-password Opaque 1 3m50s secret/eda-database-configuration Opaque 6 3m50s secret/eda-secret-tls kubernetes.io/tls 2 3m50s secret/eda-db-fields-encryption-secret Opaque 1 2m51s デプロイが完了（Reconciliation Loop が収束）したら、https://eda.example.com/ で Web UI にアクセスできます。\nデモ： ルールブックの利用（Webhook の場合） EDA Server ができたので、デモとしてソースに Webhook（ansible.eda.webhook）を利用した次のルールブックを実際に動かせるように構成します。 実際のファイルは GitHub のリポジトリ に配置しています。\n--- - name: Demo Ruleset for Webhook hosts: all sources: - ansible.eda.webhook: host: 0.0.0.0 port: 5000 rules: - name: Run Demo Job Template condition: event.payload.message == \u0026#34;Hello EDA\u0026#34; action: run_job_template: name: Demo Job Template organization: Default 中身は、Webhook で投げられたデータの message フィールドの中身が Hello EDA だった場合に、AWX で Demo Job Template を実行するルールセットです。\nEDA Server の構成 EDA Server で実際にこれを動かすには、大まかには次の作業が必要です。\nAWX でトークンを発行して EDA Server に登録する Decision Environment（DE）を EDA Server に登録する ルールブックを含むリポジトリをプロジェクトとして EDA Server に登録する ルールブックを有効化（Activate）する また、ルールセットのソースにした Webhook のエンドポイントを Kubernetes クラスタ外から叩けるようするため、詳細は後述しますが、今回は上記に加えて Ingress も追加でデプロイします。\nIngress 以外に Kubernetes 固有の手順はほとんどないため、以下、ポイントだけ紹介します。\nAWX でのトークンの発行と EDA Server への登録 EDA Server が AWX の Job Template を実行できるようにするため、AWX のトークン（Write 権限）を発行して、EDA Server に登録します。AWX のインスタンスに対して kubectl を使える場合は、コマンドで発行してしまうのが手軽です。\n$ kubectl -n awx exec deployment/awx-task -- awx-manage create_oauth2_token --user=admin 4sIZrWXi**************8xChmahb 発行できたら、EDA Server の User details の Controller Tokens タブで登録します。\nDecision Environment（DE）の EDA Server への登録 詳細は後述しますが、EDA Server でルールブックを有効化すると、新しい Pod が起動 して、その中で ansible-rulebook コマンドが実行されます。この Pod が Decision Environment（DE）です。\nこの DE に利用されるコンテナイメージは、あらかじめ EDA Server に登録が必要です。\n今回は、最小限の DE として、既成の quay.io/ansible/ansible-rulebook:latest を Decision Environments のページで登録します。\nルールブックを含むリポジトリの EDA Server への登録 EDA Server で利用するルールブックは、リモートの Git リポジトリ（の rulebooks または extensions/eda/rulebooks ディレクトリ下）に配置されている必要があります。\n本エントリで紹介しているルールブックは GitHub のリポジトリ にそのまま使えるよう配置しているため、今回は Projects のページで https://github.com/kurokobo/awx-on-k3s.git を登録します。\n追加すると、自動でプロジェクトの同期（Git リポジトリのクローン）が（Worker 上で）開始されます。しばらく待って Status が Completed になれば正常です。\n失敗した場合は、Web UI ではエラーメッセージの表示が不十分なので、Worker の Pod のログを見るとよいでしょう。\nルールブックの有効化（Activate） ここまでの作業で、当初の目的であるルールブックの起動（ansible-rulebook の実行）の準備ができました。実際に起動するには、Rulebook Activations のページで、Project と Rulebook、Decision Environment を指定して、ルールブックを有効化します。このとき、Rulebook activation enabled? を Enabled にしていれば、保存した段階でルールブックが起動されます。\nしばらく待って、Status が Running になれば正常です。\n何らかの理由で失敗しても、残念ながら Web UI 上ではエラーメッセージは表示してくれないので、Worker か後述する Activation Job の Pod のログを見るとよいでしょう。\nなお、定義した Rulebook Activation には一意の Activation ID が付与されます。Web UI の末尾の数字（例えば http://eda.example.com/eda/rulebook-activations/details/1 なら 1）で確認しておくと、このあとの探索がしやすくなります（ID が Web UI に表示されるようにする PR を軽率に送ってしまいました、どうなることやら）。\nKubernetes 上でのルールブックの動作の仕方 ここまでで、Kubernetes 上で無事に ansible-rulebook が実行され、イベントを待ち受ける状態になっています。ここから、少し実装を掘り下げていきます。\nKubernetes 環境でルールブックを有効化すると、EDA Server（の Worker）は次のリソースを Kubernetes 上に作成します。\nJob Service（必要な場合のみ） 作成された Job は、Activation ID を使って絞り込むと正確に同定できます（ラベルではなく名前で activation-job-${ACTIVATION_ID} を get しても同じです）。\n$ ACTIVATION_ID=1 $ kubectl -n eda get job -l activation-id=${ACTIVATION_ID} NAME COMPLETIONS DURATION AGE activation-job-1 0/1 4m43s 4m43s この Job により、新しい Pod が起動されます。この Pod が Activation Job Pod であり、Decision Environment の実体 です。Job から起動された Pod の名前にはランダムな文字列が付与されるので、探すときはラベルを使って絞り込むとラクです。\n$ JOB_NAME=activation-job-${ACTIVATION_ID} $ kubectl -n eda get pod -l job-name=${JOB_NAME} NAME READY STATUS RESTARTS AGE activation-job-1-h9kjt 1/1 Running 0 11m Pod の詳細を見ると、ansible-rulebook が worker モードで起動していることがわかります。この ansible-runner は、Daphne（WebSocket）経由で EDA Server からルールブックの情報などを受け取って動作しています。\n$ kubectl -n eda describe pod -l job-name=${JOB_NAME} ... Containers: activation-pod-1: ... Port: 5000/TCP ... Command: ansible-rulebook Args: --worker ... また、ルールブック中でルールセットの sources に host と port が指定されている場合は、この Pod は上記の通り指定のポートを待ち受けるように構成されます。さらに、これに対応した Service も EDA Server により作成されます。\n$ kubectl -n eda get service -l job-name=${JOB_NAME} NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE activation-job-1-5000 ClusterIP 10.43.221.234 \u0026lt;none\u0026gt; 5000/TCP 11m つまり、ルールセットで定義したソース（今回は Webhook）を実際に待ち受けているのはこの Pod です。Pod のポートの構成とこの Service の作成が EDA Server により行われることで、ルールセットのソースで指定した待ち受けポートが 公開され、今回であれば Webhook が叩ける状態が実現されています（逆に言えば、Webhook はどうにかしてこの Pod に届くように投げなければいけません）。\nただし、現在の実装では Service の type は ClusterIP で固定（正確には、type が指定されないためデフォルトの ClusterIP が使われる）のため、公開範囲は Kubernetes クラスタ内に限られます。クラスタ外から叩きたい場合は、何らかの工夫が必要です。\nWebhook による動作確認 ここまでで、Webhook を叩ける状態ができています。とはいえ、Kubernetes クラスタ内から叩けることを確認してもあまりおもしろくないので、ここでは前述の Service を Ingress でクラスタ外に公開することを考えます。\n--- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: namespace: eda name: eda-ingress-webhook spec: tls: - hosts: - eda.example.com secretName: eda-secret-tls rules: - host: eda.example.com http: paths: - path: /webhooks/demo pathType: ImplementationSpecific backend: service: name: activation-job-1-5000 port: number: 5000 今回は、EDA Server の Web UI と同じ host を指定して、path で階層を下げました。これにより、Web UI と同じ URL で Webhook にもアクセスできる ようになります。service には、前述の手順で確認した Service 名を指定しています。\nこれを適用して、あとは外部から curl などで適当に JSON を投げるだけです。ルールセットで指定した通り、message フィールドに Hello EDA を含めます。\n$ curl -k \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{\u0026#34;message\u0026#34;: \u0026#34;Hello EDA\u0026#34;}\u0026#39; \\ https://eda.example.com/webhooks/demo 正常に動作すれば、EDA Server の Rule Audit ページや AWX の Jobs ページなどで、ルール通りにアクションが実行されたことが確認できます。\nデモ： ルールブックの利用（MQTT の場合） Webhook だと個人的にどうしてもイベントドリヴンっぽさがいまいち充分でない気がしてしまうので、MQTT をソースにしたルールセットも試しておきます。実際のファイルは GitHub のリポジトリ に配置しています。\n--- - name: Demo Ruleset for MQTT hosts: all sources: - ansible.eda.mqtt: host: \u0026#34;{{ mqtt_host }}\u0026#34; port: \u0026#34;{{ mqtt_port }}\u0026#34; topic: \u0026#34;{{ mqtt_topic }}\u0026#34; rules: - name: Run Demo Job Template condition: event.message == \u0026#34;Hello EDA\u0026#34; action: run_job_template: name: Demo Job Template organization: Default ソースは ansible.eda.mqtt です。お試しなので匿名かつ非暗号化の素の MQTT の想定で作っています。ホスト名やポート番号、トピックには固定値を埋め込んでもよいのですが、今回は ルールブック変数 のデモも兼ねて変数にしています。\nルールは、MQTT でサブスクライブしたメッセージ（JSON 形式）の message フィールドが Hello EDA だったときに、AWX で Job Template を起動するものです。\nMQTT Broker の構成 何でもよいのでとにかく用意します。インタネット上から丸見えでよければ test.mosquitto.org も使えます。自前で建てたい場合は kustomization.yaml を用意した のでこれを放り込めば動くはずです（nodePort の 31883 で待ち受けます）。\nEDA Server の構成 Webhook のときとほぼ同じなので、違うところだけ紹介します。\n利用する DE の指定 ソースは ansible.eda.mqtt、と書いたものの、実はまだそれを使えるようにする PR はマージされていません。かつ、そのままだと動きません。そこで、その PR のファイルを少し修正して使えるようにしたイメージ をビルドして docker.io/kurokobo/ansible-rulebook:v1.0.1-mqtt として公開したので、今回はこれを使います（この修正点は、PR に対する PR として送っています）。\nルールブック変数の指定 Rulebook Activation を作成する際に、Variables 欄でルールブックで使っている変数の実際の値を指定します。例えば以下です。\nmqtt_host: mqtt.example.com mqtt_port: 31883 mqtt_topic: demo なお、Variables 欄のツールチップには、この欄が ansible-playbook に対する -e または --extra-vars として使われる旨の記載がありますが、これは 誤り（Issue）で、正しくは ansible-rulebook に対して --vars で指定するファイルの中身に相当 します（実際にはこの情報は WebSocket 経由で送られるので、本当に --vars が使われているわけではありません）。\n動作の確認 ルールブックが起動できたら、MQTT で JSON 形式のメッセージを送ると動作します。\ndocker run -it --rm efrecon/mqtt-client pub \\ -h mqtt.example.com \\ -p 31883 \\ -t demo \\ -m \u0026#39;{\u0026#34;message\u0026#34;: \u0026#34;Hello EDA\u0026#34;}\u0026#39; 補足 いくつか補足です。\nプライベート Git リポジトリの利用 プロジェクトとして追加したいリポジトリに認証が必要な場合は、Credentials で GitHub personal access token（または GitLab...）を指定してユーザ名とパスワードを保存し、プロジェクトの追加時に指定します。\n名前が GitHub / GitLab なので不安になりますが、実際には特にそれらのサービス固有の実装がされているわけではなく、ユーザ名とパスワードを URL に埋め込んで git clone に渡しているだけなので、期待通り動作します。\nなお、リモートの Git リポジトリが HTTPS の場合、証明書の検証は無効にできない（Issue、PR、PR）ので注意が必要です（がんばりたい場合は Worker のイメージに証明書を埋め込むなどでしょうか……）。\nWorker の数と処理の並列度の制限 EDA Server は、プロジェクトの同期やルールブックの実行などの一部の処理を Worker と呼ばれるコンポーネント（Kubernetes 上では *-worker の Pod）に投げています。ここの実装は RQ（Redis Queue） ですが、大事な点は、Worker は同時に一つのキューしかさばけない ことです。このため、Worker の数がそのまま処理の並列度の上限 になってきます。\n今回の実装例のように少し古い EDA Server のイメージを使っている場合、Worker は一種類しかないため、わかりやすいところでは ルールブックの有効化で Worker を使い切っているとプロジェクトの同期が Worker に受け取ってもらえず Pending になる など、直感に反するトラブルを招きます。\n最新の EDA Server では、ルールブックの有効化を扱う Activation Worker と プロジェクトの同期などそれ以外を扱う Default Worker の二種類に分けられたため、前述の直感的でない動作不良は発生しにくくなっていますが、それでも 同時に起動できるルールブックの数 が Activation Worker の数に依存する ことになるため、引き続き一定の注意は必要です。\nOperator の 0.0.5 ではこの二種類の Worker のデプロイに対応できていないため、今回の実装例では少し古い EDA Server を利用しましたが、将来的には二種類それぞれレプリカ数を別々に指定できるようになるはず（Issue、PR）です。\nおわりに EDA Server を Operator で K3s 上にデプロイして、その動作を確認しました。Operator も EDA Server もまだまだこなれていない感がありますが、Red Hat の中の方々だけでなく、コミュニティも含めてわいわいしていけるとよいですね。\n","date":"2023-08-09T13:49:39Z","image":"/archives/5041/img/image-378.jpg","permalink":"/archives/5041/","title":"EDA Server を Operator でデプロイして Kubernetes 上での動作を確認する"},{"content":"はじめに 先日リリースされた Veeam Backup \u0026amp; Replication 12 で、バックアップサーバの構成データベースとしてバンドルされている RDBMS が、従来の Microsoft SQL Server Express から PostgreSQL に変更 されました。新規インストール時は、デフォルトで PostgreSQL が一緒に導入されます。\nIf you do not prepare a database engine in advance, Veeam Backup \u0026amp; Replication will automatically install PostgreSQL 15.1 locally on the backup server. Before You Begin - User Guide for VMware vSphere\rSQL Server はそもそもバンドルされなくなったため、事前に自分で用意しない限り選択できません。\n[For PostgreSQL] You can use an already installed PostgreSQL instance or install a new one. … [For Microsoft SQL Server] You can use an already installed Microsoft SQL Server database only. Step 8. Specify Database Engine and Instance - User Guide for VMware vSphere\r一方で、11 から 12 にアップグレードする場合は、構成データベースは SQL Server のまま変更されません。SQL Server も引き続きサポートされるため、そのままでも何ら問題はありませんが、もし PostgreSQL を使いたい場合 は、アップグレード後に自分で移行 する必要があります。\nちょうど自宅ラボに眠っている 11 の環境があり、NFR ライセンス を使って興味半分でアップグレードしたので、本エントリでは、Veeam Backup \u0026amp; Replication 12 の構成データベースを SQL Server から PostgreSQL に移行する流れを、ごく簡単に紹介します。\n作業の流れ 基本的にはドキュメントに従って作業します。構成データベースの移行に関する Veeam の公式ブログも併せて参考にできます。\nMigrating Configuration Database to PostgreSQL Server - User Guide for VMware vSphere Switch from SQL Server to PostgreSQL for Veeam 大きくは以下の流れです。\nVeeam Backup \u0026amp; Replication 12 にアップグレードする PostgreSQL をインストールして構成する ジョブを無効化する 設定情報のバックアップを取得する 設定情報のバックアップを移行モードでリストアする PostgreSQL をチューニングする SQL Server を停止（削除）する つまり、SQL Server から設定情報のバックアップを取得して、PostgreSQL に対してリストアする形です。\n作業のポイント ドキュメントに従えば迷うところもあまりないので、各作業のポイントだけ簡単に紹介します。\n1. Veeam Backup \u0026amp; Replication 12 にアップグレードする ドキュメントを見ながら作業します。\nUpgrading to Veeam Backup \u0026amp; Replication 12 - User Guide for VMware vSphere 実際に試した範囲では、ウィザードをぽちぽちするだけで順当に終わってしまったので、特にコメントはありません。\n2. PostgreSQL をインストールして構成する Veeam Backup \u0026amp; Replication 12 を新規にインストールするときとは異なり、アップグレード後にデータベースを移行したい場合は、PostgreSQL は自分で用意する必要があります。\nPostgreSQL のインストーラの準備 PostgreSQL のインストーラは、Veeam Backup \u0026amp; Replication 12 の ISO ファイル中、Redistr\\x64\\PostgreSQL\\15.1-1 にも配置されていますが、これはバージョンが 15.1 と少し古いものです。\nドキュメントのシステム要件のページ を見ると、自分で 15.x の最新をダウンロードして使うことが推奨されているので、できればその方がよいでしょう。\nPostgreSQL 15.1 is included in the Veeam Backup \u0026amp; Replication setup, but we strongly recommend to download and install the latest PostgreSQL 15.x version) System Requirements - User Guide for VMware vSphere\r本エントリ公開時点の最新は 15.3 です。以下のページからダウンロードできます。\nCommunity DL Page 以下は 15.3 を前提とした記述です。\nPostgreSQL のインストール すべてデフォルトのまま適当にインストールしても大丈夫そうです。コンポーネントの選択時、pgAdmin と Stack Builder は外しても問題ありません。\nPostgreSQL の初期設定 Veeam から PostgreSQL に接続できるよう、認証を構成します。選択肢はだいたい以下の具合になりそうです。\n公式のブログ を参考に、Veeam 用のロールをひとつ作る Veeam Backup \u0026amp; Replication 12 の新規インストール時のデフォルトを参考に、SSPI 認証を構成する デフォルトの postgres ロールをそのまま使う Veeam 用のロールをひとつ作る 場合は、公式のブログ を参考にスーパユーザ権限を持たせて作るとよさそうです（権限が強すぎる気もしますが、必要な最小限の権限がどこにも書いていないようなので不明です）。\nPS\u0026gt; \u0026amp; \u0026#39;C:\\Program Files\\PostgreSQL\\15\\bin\\psql.exe\u0026#39; -U postgres ユーザー postgres のパスワード: psql (15.3) \u0026#34;help\u0026#34;でヘルプを表示します。 postgres=# CREATE ROLE veeam WITH SUPERUSER LOGIN PASSWORD \u0026#39;veeam\u0026#39;; CREATE ROLE SSPI 認証を構成する 場合は、新規インストール時のデフォルトで Windows の veeam ユーザが PostgreSQL の postgres にマップされていたので、これに倣うのも手です（これも権限が強すぎる気もしますが……）。新規インストール直後の pg_ident.conf と pg_hba.conf は次の状態だったので、これをそのまま適用して PostgreSQL をいちど再起動すれば作業は完了です。\nPS\u0026gt; gc \u0026#39;C:\\Program Files\\PostgreSQL\\15\\data\\pg_ident.conf\u0026#39; ... # MAPNAME SYSTEM-USERNAME PG-USERNAME veeam Administrator@\u0026lt;大文字ホスト名\u0026gt; postgres veeam \u0026#34;SYSTEM@NT AUTHORITY\u0026#34; postgres PS\u0026gt; gc \u0026#39;C:\\Program Files\\PostgreSQL\\15\\data\\pg_hba.conf\u0026#39; ... # TYPE DATABASE USER ADDRESS METHOD # \u0026#34;local\u0026#34; is for Unix domain socket connections only local all all sspi map=veeam # IPv4 local connections: host all all 127.0.0.1/32 sspi map=veeam # IPv6 local connections: host all all ::1/128 sspi map=veeam # Allow replication connections from localhost, by a user with the # replication privilege. local replication all sspi map=veeam host replication all 127.0.0.1/32 sspi map=veeam host replication all ::1/128 sspi map=veeam postgres ロールをそのまま使う 場合は、特に追加の作業はありません。\nなお、PostgreSQL のパラメータを Veeam Backup \u0026amp; Replication 用にするいわゆるチューニングっぽいことは後ほど行うので、この段階では何もしません。\n3. ジョブを無効化する Veeam Backup \u0026amp; Replication のジョブをすべて無効化し、移行作業が終わるまで動かない状態にします。移行は設定情報のバックアップとリストアで行いますが、バックアップの取得後からリストアまでの間にジョブが動いてしまうと、データベース内の情報と実データとの間で不整合が発生するためです。\n無効化していないジョブがあっても後続の作業は完遂できますが、後述する通り、後の手順で警告が表示されます。\n4. 設定情報のバックアップを取得する 手動で設定情報のバックアップ（Configuration Backup）を取得します。\n5. 設定情報のバックアップを移行モードでリストアする 設定情報のリストアのウィザードを起動すると、最初の画面でリストアのモードが選択できます。ここで、移行（Migrate）を選択します。\nその後、リストアポイントを選択してさらにウィザードを進めると、構成データベースへの接続情報を入力する画面に遷移します。PostgreSQL 側で SSPI 認証を利用する場合は Windows authentication using credentials of service account、Veeam 用のロールを作った場合か既成の postgres ロールをそのまま使う場合は Native authentication using the following credentials で進めます。\n手元の環境では、インスタンス名（instance name）に自ホスト名を含めて入力して Connect を押下したら、文字化けしたエラーが出ました。\n解読していませんが、IPv6 っぽい部分があるので、たぶんホスト名が IPv6 のリンクローカルアドレスに解決されてしまい、それが pg_hba.conf 的に許容されなかったものと思われます。インスタンス名（instance name）を localhost:5432 や 127.0.0.1:5432 などにすると先に進めました。\nあとはウィザードを進めて待てば、移行処理は終わりです。\nなお、前項の手順でジョブの無効化を行っていない（設定情報のバックアップに無効化されていないジョブが含まれている）場合、リストア処理中に次の警告が表示されます。\n無視して進めますし、有効化されたジョブが残っていてもデータの不整合が起きないようにいい感じにやってくれそうな雰囲気もありますが、できるだけ事前に無効化しておくほうがよいでしょう。\n6. PostgreSQL をチューニングする この段階では、PostgreSQL のパラメータはデフォルトのままです。これを Veeam Backup \u0026amp; Replication にあわせて調整します。\nとはいっても、専用の PowerShell コマンドレットが用意されているので、叩いて待つだけです。\nPS\u0026gt; Set-VBRPSQLDatabaseServerLimits 警告: New postgres configuration was set. Restart postgres service manually to apply changes. Make sure there are no running jobs before restart. 実行すると、そのサーバの CPU コア数やメモリ容量に応じて、PostgreSQL のリソース関連の調整が行われるようです。具体的には、 ALTER SYSTEM 文がいくつか実行されています。\n自宅の 4 vCPU で 8 GB RAM な小さいバックアップサーバでは、実行後に postgresql.auto.conf を確認すると、次のような調整が行われていました。\nPS\u0026gt; gc \u0026#39;C:\\Program Files\\PostgreSQL\\15\\data\\postgresql.auto.conf\u0026#39; # Do not edit this file manually! # It will be overwritten by the ALTER SYSTEM command. max_connections = \u0026#39;3000\u0026#39; effective_cache_size = \u0026#39;5734MB\u0026#39; checkpoint_completion_target = \u0026#39;0.9\u0026#39; wal_buffers = \u0026#39;16MB\u0026#39; default_statistics_target = \u0026#39;100\u0026#39; random_page_cost = \u0026#39;1.1\u0026#39; max_worker_processes = \u0026#39;4\u0026#39; max_parallel_workers_per_gather = \u0026#39;2\u0026#39; max_parallel_workers = \u0026#39;4\u0026#39; max_parallel_maintenance_workers = \u0026#39;2\u0026#39; min_wal_size = \u0026#39;2GB\u0026#39; max_wal_size = \u0026#39;8GB\u0026#39; hash_mem_multiplier = \u0026#39;2\u0026#39; log_temp_files = \u0026#39;10MB\u0026#39; log_lock_waits = \u0026#39;on\u0026#39; join_collapse_limit = \u0026#39;8\u0026#39; geqo_threshold = \u0026#39;10\u0026#39; log_line_prefix = \u0026#39;%m [%p] %q[user=%u,db=%d,app=%a] \u0026#39; jit = \u0026#39;off\u0026#39; log_autovacuum_min_duration = \u0026#39;60s\u0026#39; log_min_duration_statement = \u0026#39;1000\u0026#39; log_statement_sample_rate = \u0026#39;0.01\u0026#39; log_rotation_age = \u0026#39;10d\u0026#39; shared_buffers = \u0026#39;512MB\u0026#39; maintenance_work_mem = \u0026#39;410MB\u0026#39; work_mem = \u0026#39;4368kB\u0026#39; なお、先の Set-VBRPSQLDatabaseServerLimits に -DumpToFile パラメータを渡せば、実行される SQL 文をファイルとして生成できます。\nPS\u0026gt; Set-VBRPSQLDatabaseServerLimits -DumpToFile C:\\dump.sql 警告: SQL Configuration script was dumped to file \u0026#39;C:\\dump.sql\u0026#39;. Run it and restart postgres service manually to apply changes. Make sure there are no running jobs before restart. PS\u0026gt; gc C:\\dump.sql ALTER SYSTEM SET max_connections = \u0026#39;3000\u0026#39;; ALTER SYSTEM SET effective_cache_size = \u0026#39;5734MB\u0026#39;; ALTER SYSTEM SET checkpoint_completion_target = \u0026#39;0.9\u0026#39;; ... 設定が適用できたら、PostgreSQL を再起動します。\nPS\u0026gt; Restart-Service -Name postgresql-x64-15 7. SQL Server を停止（削除）する ここまでの作業で、Veeam は PostgreSQL のみを利用する状態に切り替わっています。\n古い SQL Server を停止して、Veeam Backup \u0026amp; Replication の動作確認を行い、問題なければ SQL Server に関連するモノを全部アンインストールして、作業は完了です。\nなお、Enterprise Manager を利用している場合は、構成次第で暗号化キーの再設定が必要になるようです。詳細はドキュメントの Step 4 に記載があります。\nまた、その他の事後作業として、設定情報のバックアップの再設定、ローカルリポジトリの再追加と再マップ、ジョブの有効化なども ドキュメントの Step 5 で挙げられています。必要に応じて作業します。\nおわりに Veeam Backup \u0026amp; Replication 12 の構成データベースを SQL Server から PostgreSQL に移行する流れを簡単に紹介しました。\nデフォルトの構成データベースが PostgreSQL になったことで、これまで Microsoft SQL Server Express を使うことで生じていたさまざまな制約から解放されたことはうれしい変更です。大規模な環境でも SQL Server を別のサーバに用意しなくてもよくなったので、構成が複雑になりがちな Veeam も、少しだけシンプルにできそうですね。\n","date":"2023-06-27T07:38:25Z","image":"/archives/5022/img/image-377.png","permalink":"/archives/5022/","title":"Veeam Backup \u0026 Replication 12 の構成データベースを SQL Server から PostgreSQL に移行する"},{"content":"はじめに 先日、Ansible Builder がメジャバージョンアップし、3.0.0 がリリースされました。\nRelease 3.0.0 - ansible/ansible-builder Ansible Builder は 本ブログでも過去に取り上げています が、設定ファイルで記述する内容に大きめの変更が入っているため、本エントリで改めて簡単に紹介します。\n基本的な考え方 Ansible Builder のコンセプト自体は従来と変わっていないため、設定ファイルを用意してビルド用のコマンドを実行する流れはそのままです（過去のエントリ でも紹介しています）。\n一方で、設定ファイルのスキーマのバージョンに新しく 3 が定義され、大きく以下の点が変更されています。\n専用のベースイメージが撤廃され、ベースイメージは OS のプレーンなイメージに Python と Ansible のバージョンが指定可能に ビルドのステップの細かなカスタマイズが可能に 今回は、新しいスキーマの実際の設定ファイルを用いて、上記の点を含めた Ansible Builder 3 での設定ファイルの記述内容とビルドの方法を簡単に紹介します。\n設定ファイルの準備 実際のファイルは GitHub のリポジトリ にも配置しています。また、指定できる項目の完全な一覧と詳細は、公式のドキュメント で確認できます。\n設定ファイル例 Ansible Builder 3 で利用できる、新しいスキーマ（バージョン 3）の設定ファイルの例です。\n--- version: 3 images: base_image: name: quay.io/centos/centos:stream9-minimal options: package_manager_path: /usr/bin/microdnf dependencies: python_interpreter: package_system: python3.11 python_path: /usr/bin/python3.11 ansible_core: package_pip: ansible-core~=2.15 ansible_runner: package_pip: ansible-runner~=2.3 system: dependencies/bindep.txt python: dependencies/requirements.txt galaxy: dependencies/requirements.yml additional_build_files: - src: files/ansible.cfg dest: configs additional_build_steps: append_base: - RUN alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 0 prepend_galaxy: - ADD _build/configs/ansible.cfg ~/.ansible.cfg 以下、上から順に紹介します。\nベースイメージの指定 images 下 で、EE のベースになるイメージを指定します。\nimages: base_image: name: quay.io/centos/centos:stream9-minimal 従来の Ansible Builder では専用のベースイメージ（quay.io/ansible/ansible-runner）を使ってビルドする形でしたが、これは撤廃 され、バージョン 3 では RHEL ファミリの OS のプレーンなイメージをそのまま使える ようになっています。\n今回の例では、CentOS Stream 9 の Minimal イメージを指定しています。ドキュメント でも一部例示されていますが、他の選択肢として、例えば認証が不要なものでは次のイメージが挙げられます。\nquay.io/centos/centos:stream9 registry.fedoraproject.org/fedora:38 registry.access.redhat.com/ubi9/ubi-minimal:9.2（docker.io/redhat/ubi9-minimal:9.2） また、ビルドに Podman を利用する場合は、ドキュメント の通り、イメージの署名の検証に関する設定もできます（今回は Docker を使うため設定していません）。\n補足： 利用できるベースイメージは現時点では RHEL ファミリのみ ベースイメージに OS のプレーンなイメージをそのまま使えるようになったとはいえ、現時点では前述の通り あくまで RHEL ファミリの OS に限定 される点は注意が必要です。\n技術的には、OS にパッケージを追加するために内部で利用されている bindep は幅広いディストリビューションに対応していますし、後述のようにビルド中に利用するパッケージマネージャのパスもオプションで指定できます。であれば、一見すると RHEL ファミリでない EE（たとえば Ubuntu ベースの EE）も作れそうに思えてしまいますが、現在の実装では、パッケージマネージャに渡す引数がハードコードされていたり、中で ensurepip が使われていたりするため、ビルドは失敗します。\nRHEL ファミリ以外のディストリビューションのサポートに対しては メンバの方から慎重な姿勢が提示されており、まずは方針や設計の原則を固めるべきとされています。少なくとも特定のディストリビューションに特化した PR がサクサクとマージされていくようなことはなさそうです。\nオプションの指定 options 下 では細かいオプションを指定できます（詳細は ドキュメント で確認できます）。\noptions: package_manager_path: /usr/bin/microdnf 今回は、前述した通りベースイメージに CentOS Stream 9 の Minimal イメージを使うため、ビルドのステップ中で利用されるパッケージマネージャに（デフォルトの dnf ではなく）microdnf を使うよう指定しています。\n依存関係の指定 dependencies 下 で、EE に導入するパッケージ群を細かく指定できます。\ndependencies: python_interpreter: package_system: python3.11 python_path: /usr/bin/python3.11 ansible_core: package_pip: ansible-core~=2.15 ansible_runner: package_pip: ansible-runner~=2.3 system: dependencies/bindep.txt python: dependencies/requirements.txt galaxy: dependencies/requirements.yml 従来の Ansible Builder のアーキテクチャでは、Python と Ansible、Ansible Runner のバージョンは、基本的には利用するベースイメージに依存してしまい、カスタマイズの余地が大きくはありませんでした。\nバージョン 3 では、python_interpreter、ansible_core、ansible_runner を使ってこれらが指定できるようになっています。今回の例では、Python 3.11、Ansible 2.15、Ansible Runner 2.3 をそれぞれ指定しています。\nsystem、python、galaxy では、従来と同様、導入したい OS パッケージや Python モジュール、Ansible の Collection や Role を指定できます。今回の例では（従来と同様に）別に用意したテキストファイルと YAML ファイルを指定しています（実物は リポジトリ に置いています）が、YAML の Multiline Strings やリストを使ったベタ書きもできるようになっています（ドキュメント に例があります）。\n補足： 依存関係は隅々まで明示が必要 従来の Ansible Builder で利用されていた専用のベースイメージは、専用なだけあって、Ansible で必要になりがちな OS パッケージや Python モジュール（sshpass、krb5-devel、pywinrm や paramiko など）はあらかじめバンドルされていました。このため、ユーザは細かな依存関係をあまり気にしなくてもどうにかなる場合が多かった印象があります。\n一方でバージョン 3 では、OS のプレーンなイメージをベースに組み立てるようになったことで、逆に言えば 自分のプレイブックの動作に必要なモノは隅から隅まで全部明示しないとダメ になってしまったということでもあります。例えば、今回のように Minimal イメージをベースにした場合、そもそも openssh-clients すら自分で system 下で明示しないと、ターゲットノードへの SSH 接続もできません。\nバージョン 3 で EE をビルドする場合、ビルドした EE でいざプレイブックを動かそうとしたときにパッケージやモジュールが足りなくて怒られてしまう可能性は、専用のベースイメージを使った場合よりは高くなりそうです。\nなお、従来の専用のベースイメージに導入されていたパッケージ群は、Ansible Runner のリポジトリの過去の断面から確認できる ため、困ったときの足がかりにはできそうです。\n追加のファイルの指定 additional_build_files 下 で、ビルドに利用する追加のファイルを指定できます。\nadditional_build_files: - src: files/ansible.cfg dest: configs ここで指定したファイルは、後述の追加のビルドのステップ（additional_build_steps）で参照できるようになります。今回は、ビルドのステップ中で実行される ansible-galaxy コマンドのための ansible.cfg をカスタマイズする想定で記載しています。\n少しわかりにくいですが、ここで指定する dest は EE 内のパスではなく、ビルド時にローカルに作成される context/_build ディレクトリ内のパスです。ビルドの過程で必要なファイルをいちど context/_build 下に集約し、集約されたファイルを additional_build_steps 中で利用する考え方です。\n追加のビルドのステップの指定 additional_build_steps 下 で、生成される Dockerfile（Containerfile）中のビルドの各フェイズの前後に任意のコマンドを追加できます。\nadditional_build_steps: append_base: - RUN alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 0 prepend_galaxy: - ADD _build/configs/ansible.cfg ~/.ansible.cfg 今回は、python3.11 を python3 として実行できるようにするための RUN alternatives ... をベースイメージに追加しています（AWX から渡されるインベントリスクリプトにハードコードされている Shebang の関係で、AWX で利用する EE では python3 が必須です）。\nまた、先述の additional_build_files で指定した ansible.cfg ファイルを、ビルド中の ansible-galaxy による Collection のインストール時に使えるよう、ADD ... を追加しています。パスがややこしいですが、additional_build_files で dest を configs にしていたため、当該ファイルはビルド時にローカルの context/_build/configs 下にコピーされ、これが additional_build_steps ではビルドのコンテキスト（context）からの相対パス（_build/configs 下）で参照できるようになっています。\n補足： ビルドのステップいろいろ ビルドは、全部で Base、Galaxy、Builder、Final の 4 つのステージで行われます。概略はドキュメント でも触れられていますが、それぞれ以下の処理です。\nBase 指定された OS イメージに Python と Ansible と Ansible Runner を導入する このステージでできたイメージが、以降の全てのステージのベースイメージとして利用される Galaxy 指定された依存関係に基づいて、Ansible の Collection や Role を所定のパスにインストールする Builder 指定された依存関係に基づいて、必要な OS のパッケージや Python のモジュールを（Collection や Role の依存関係を含め解決し）整理してインストールする Final Galazy ステージと Builder ステージのイメージから必要なファイル群を抽出してインストールし、EE イメージとして仕上げる additional_build_steps では、これらそれぞれのステージの前（prepend_*）と後（append_*）に任意の処理を追加できます（ドキュメント で一覧されています）。今回は append_base と prepend_galaxy を使っています。\nその他の記述内容 version は設定ファイル自体のスキーマのバージョンの指定です。今回指定している 3 が、Ansible Builder 3 で追加された新しいバージョンです。記述がない場合は従来の 1 として扱われます（つまり、従来の設定ファイルも引き続き利用できます）。\nversion: 3 このほか、紹介したサンプルでは使っていませんが、ビルド時の引数を指定できる build_arg_defaults も用意されています。\nビルド 設定ファイルが用意できたら、付与したいタグとともに build コマンドを実行すると、EE のイメージができあがります。細かい進捗を見るには --verbosity 3 が便利です。\n$ ansible-builder build --tag registry.example.com/ansible/ee:2.15-custom --container-runtime docker --verbosity 3 Ansible Builder is generating your execution environment build context. File context/_build/requirements.yml will be created. File context/_build/requirements.txt will be created. File context/_build/bindep.txt will be created. Creating context/_build/configs File context/_build/configs/ansible.cfg will be created. File context/_build/scripts/assemble will be created. File context/_build/scripts/install-from-bindep will be created. File context/_build/scripts/introspect.py will be created. File context/_build/scripts/check_galaxy will be created. File context/_build/scripts/check_ansible will be created. File context/_build/scripts/entrypoint will be created. Ansible Builder is building your execution environment image. Tags: registry.example.com/ansible/ee:2.15-custom Running command: docker build -f context/Dockerfile -t registry.example.com/ansible/ee:2.15-custom context Sending build context to Docker daemon 50.18kB Step 1/77 : ARG EE_BASE_IMAGE=\u0026#34;quay.io/centos/centos:stream9-minimal\u0026#34; ... Step 77/77 : CMD [\u0026#34;bash\u0026#34;] ---\u0026gt; Running in 2eff2dca84d2 Removing intermediate container 2eff2dca84d2 ---\u0026gt; d804667597e9 Successfully built d804667597e9 Successfully tagged registry.example.com/ansible/ee:2.15-custom Complete! The build context can be found at: /home/********/awx-on-k3s/builder/context ビルドのコンテキストは、./context に生成されています。dependencies や additional_build_files で指定したファイル群もすべてこの中に保持されます。このディレクトリの中身さえあれば Ansible Builder が無くてもビルドできるため、EE イメージの構成だけを管理したい場合や、CI のパイプラインにビルドを組み込みたい場合にも活用できます。\n$ tree context context |-- Dockerfile `-- _build |-- bindep.txt |-- configs | `-- ansible.cfg |-- requirements.txt |-- requirements.yml `-- scripts |-- assemble |-- check_ansible |-- check_galaxy |-- entrypoint |-- install-from-bindep `-- introspect.py 3 directories, 11 files Dockerfile からは、各ステージの内容が確認できます。\n$ cat context/Dockerfile ARG EE_BASE_IMAGE=\u0026#34;quay.io/centos/centos:stream9-minimal\u0026#34; ... # Base build stage FROM $EE_BASE_IMAGE as base ... # Galaxy build stage FROM base as galaxy ... # Builder build stage FROM base as builder ... # Final build stage FROM base as final ... 補足： コンテキストの生成だけを行いたい場合 イメージのビルドは行わずにコンテキストの生成だけを行いたい場合は、create コマンドを利用できます。\n$ ansible-builder create --verbosity 3 Ansible Builder is generating your execution environment build context. File context/_build/requirements.yml will be created. File context/_build/requirements.txt will be created. File context/_build/bindep.txt will be created. Creating context/_build/configs File context/_build/configs/ansible.cfg will be created. File context/_build/scripts/assemble will be created. File context/_build/scripts/install-from-bindep will be created. File context/_build/scripts/introspect.py will be created. File context/_build/scripts/check_galaxy will be created. File context/_build/scripts/check_ansible will be created. File context/_build/scripts/entrypoint will be created. Complete! The build context can be found at: /home/********/awx-on-k3s/builder/context まとめ Ansible Builder 3 の設定ファイルの内容と使い方を簡単に紹介しました。\n専用のベースイメージが不要になってカスタマイズ範囲が広がったことはメリットですが、悪く言えば、特に依存関係まわりなどユーザが明示的に指定しなければならない範囲も同様に広がっているため、苦労が増えてしまう面もありそうです。\n","date":"2023-06-24T07:08:19Z","image":"/archives/4995/img/image-248.jpg","permalink":"/archives/4995/","title":"新しい Ansible Builder 3 で Execution Environment を作る"},{"content":"はじめに AutoMuteUs の 7.3 がリリースされました。このバージョンでは、AutoMuteUs のデータベース内の自ギルドに関するデータを CSV 形式でダウンロードできる機能 が追加されています。\nデータベース内のデータとは、簡単にいえば 戦績の集計の元ネタ です。過去の全ゲームの参加者や色や勝敗や勝因 だけでなく、各ゲームの時系列 も取得できるため、何らかの分析をしたい場合にはよい情報源にできそうです。\n一方で、ダウンロードできるのは本当に データベースの中身そのもの なので、正規化された表がそのまま出力されますし、内部的な ID も ID のままで、手がかりがないと読みにくい状態です。\nそこで本エントリでは、簡単にコマンドの使い方を紹介するとともに、CSV の中身の見方も簡単に説明します。\n概要 前述の通り、この機能は AutoMuteUs のデータベース内の自ギルドに関するデータを CSV 形式でダウンロードできる ものです。\nコマンドの構文 ダウンロードするためのコマンドは、公式のヘルプ にも記載がありますが、/download category:\u0026lt;カテゴリ\u0026gt; です。\n実行すると確認メッセージが表示され、了承すると CSV ファイルがプライベートメッセージで添付されてきます。\nコマンドで指定する カテゴリ には、次の 5 つがあります。各カテゴリで実際にダウンロードできる CSV ファイルの中身は、追って紹介します。\nカテゴリ名 内容 guild そのギルドの情報 games そのギルドで行われたゲームの一覧。開始・終了の時刻と勝因を含む users そのギルドで行われたゲームに参加したことのあるユーザの一覧 users_games そのギルドで行われた各ゲームに参加したユーザの一覧。参加時のプレイヤ名や役割、勝敗情報を含む game_events そのギルドで行われた各ゲームの詳細な時系列情報 利用上の注意 利用にあたって、次の制約があります。\n公式ボット で利用するには、AutoMuteUs プレミアム の ゴールド が必要です（セルフホストでは無条件で利用できます） ダウンロードできるのは、カテゴリごとに 24 時間に 1 回だけ です（セルフホストでも同じ制限があります） CSV ファイルの中身は ソートされていません CSV ファイルのサイズが 8 MB を超える 場合は ダウンロードできません（7.3.1 現在、Discord の添付ファイルのサイズの制約により、The interaction failed のエラーが返ります） データ収集を オプトアウト（/privacy optout）したユーザに関する情報 は、そもそもデータベースに保存されていないため、ダウンロードできる CSV ファイルにも含まれません 8 MB の制限は開発側も認識しています が、相当な数のゲームが行われたギルドでないとこのサイズには到底届かないため、現時点では配慮されていません。もしこの問題に直面したギルドがあれば、サポートの Discord などで要望をあげてもらえれば、実装を検討できると思います。\nCSV ファイルの解釈例 まずは CSV ファイルから読み取れる具体的な情報を紹介します。細かな読み取り方は後述します。\ngames カテゴリの CSV ファイルの解釈例 game_id,guild_id,connect_code,start_time,win_type,end_time, 1,8025**********6123,F3BF4835,1611451395,4,1611451752, この CSV ファイルは、次のように解釈できます。\nこのゲームのマッチ ID は F3BF4835:1 である このゲームは 2021/01/24 10:23:15（日本標準時）に開始し、同 10:29:12 に終了した 勝利したのはインポスターチームであり、勝因はサボタージュの時間切れである users_games カテゴリの CSV ファイルの解釈例 user_id,guild_id,game_id,player_name,player_color,player_role,player_won 3057**********2493,8025**********6123,1,Player1,0,0,false 3466**********4338,8025**********6123,1,Player2,7,0,false 3840**********4563,8025**********6123,1,Player3,4,1,true 5090**********1842,8025**********6123,1,Player4,3,0,false 7962**********0756,8025**********6123,1,Player5,8,0,false 7982**********4468,8025**********6123,1,Player6,9,0,false 7982**********4458,8025**********6123,1,Player7,5,1,true 7982**********6161,8025**********6123,1,Player8,1,0,false 7998**********7914,8025**********6123,1,Player9,2,0,false この CSV ファイルからは、ゲームへの参加ユーザが次の通りであったことが読み取れます。\nプレイヤ名 プレイヤの色 役割 勝敗 Player1 レッド クルー 敗北 Player2 ホワイト クルー 敗北 Player3 オレンジ インポスター 勝利 Player4 ピンク クルー 敗北 Player5 パープル クルー 敗北 Player6 ブラウン クルー 敗北 Player7 イエロー インポスター 勝利 Player8 ブルー クルー 敗北 Player9 グリーン クルー 敗北 game_events カテゴリの CSV ファイルの解釈例 event_id,user_id,game_id,event_time,event_type,payload, 1,,1,1611451395,2,1, 2,,1,1611451412,2,2, 3,3466**********4338,1,1611451498,3,{\u0026#34;Name\u0026#34;: \u0026#34;Player2\u0026#34;, \u0026#34;Color\u0026#34;: 7, \u0026#34;Action\u0026#34;: 6, \u0026#34;IsDead\u0026#34;: false, \u0026#34;Disconnected\u0026#34;: false}, 4,,1,1611451498,2,1, 5,,1,1611451507,3,{\u0026#34;Name\u0026#34;: \u0026#34;Player2\u0026#34;, \u0026#34;Color\u0026#34;: 7, \u0026#34;Action\u0026#34;: 2, \u0026#34;IsDead\u0026#34;: true, \u0026#34;Disconnected\u0026#34;: false}, 6,5090**********1842,1,1611451533,3,{\u0026#34;Name\u0026#34;: \u0026#34;Player4\u0026#34;, \u0026#34;Color\u0026#34;: 3, \u0026#34;Action\u0026#34;: 2, \u0026#34;IsDead\u0026#34;: true, \u0026#34;Disconnected\u0026#34;: false}, 7,,1,1611451534,2,2, 8,,1,1611451578,2,1, 9,,1,1611451625,2,2, 10,,1,1611451703,2,1, 11,7998**********7914,1,1611451737,3,{\u0026#34;Name\u0026#34;: \u0026#34;Player9\u0026#34;, \u0026#34;Color\u0026#34;: 2, \u0026#34;Action\u0026#34;: 2, \u0026#34;IsDead\u0026#34;: true, \u0026#34;Disconnected\u0026#34;: false}, 12,,1,1611451747,2,4, 13,,1,1611451752,2,0, この CSV ファイルからは、ゲームの時系列を次のように再現できます。\n時刻 イベント 2021/01/24 10:23:15 タスクフェイズが開始された 2021/01/24 10:23:32 緊急会議が開始された 2021/01/24 10:24:58 Player2 が追放された 2021/01/24 10:24:58 タスクフェイズが開始された 2021/01/24 10:25:33 Player4 がキルされた 2021/01/24 10:25:34 緊急会議が開始された 2021/01/24 10:26:18 タスクフェイズが開始された 2021/01/24 10:27:05 緊急会議が開始された 2021/01/24 10:28:23 タスクフェイズが開始された 2021/01/24 10:28:57 Player9 がキルされた 2021/01/24 10:29:07 ゲームオーバーになった 2021/01/24 10:29:12 ロビーに戻った CSV ファイルの読み方 ここから、前述のカテゴリごとにダウンロードできる CSV ファイルのカラムの意味を紹介します。\nguild カテゴリ guild カテゴリでは、そのギルドの情報がダウンロードできます。\n通常、中身は 1 行だけで、意味のあるカラムは guild_id と guild_name のみです。それ以外は CSV 化にあたって強制的に削除または固定値に置換されているため、意味のあるデータを含みません。\nguild_id,guild_name,premium,tx_time_unix,transferred_to,inherits_from, 8025**********6123,discord.example.com,5,,, カラム名 説明 guild_id Discord でのギルドの ID guild_name ギルドの名前 premium 意味なし（必ず 5） tx_time_unix transferred_to inherits_from 意味なし（必ず空） games カテゴリ games カテゴリでは、そのギルドで行われたゲームの一覧がダウンロードできます。\n1 行が 1 ゲームに相当します。game_id は後述の users_games カテゴリや game_events カテゴリの同名のカラムに対応しています。\nなお、connect_code と game_id を \u0026lt;connect_code\u0026gt;:\u0026lt;game_id\u0026gt; の形式で連結した文字列は、マッチ ID として /stats view match \u0026lt;マッチ ID\u0026gt; コマンドに利用できます。\n以下の例はソートしたものです。\ngame_id,guild_id,connect_code,start_time,win_type,end_time, 1,8025**********6123,F3BF4835,1611451395,4,1611451752, 2,8025**********6123,F3BF4835,1611451813,0,1611452925, 3,8025**********6123,F3BF4835,1611453125,1,1611454338, 4,8025**********6123,C02B1DE7,1611454568,1,1611455222, 5,8025**********6123,C02B1DE7,1611455317,1,1611456206, ... カラム名 説明 game_id そのゲームの一意の ID guild_id Discord でのギルドの ID connect_code AmongUsCapture との接続コード start_time そのゲームの開始時刻（エポック秒） win_type そのゲームが終了した理由（次表参照） end_time そのゲームの終了時刻（エポック秒） win_type 説明 0 クルーがインポスターを追放して勝利 1 クルーがタスクを完了して勝利 2 インポスターがクルーを追放して勝利 3 インポスターがクルーをキルして勝利 4 インポスターがサボタージュで勝利 5 インポスターがクルーの切断で勝利 6 クルーがインポスターの切断で勝利 -1 または 7 何らかの理由による異常終了 users カテゴリ users カテゴリでは、そのギルドで行われたゲームに参加したことのあるユーザの一覧がダウンロードできます。\n1 行が 1 ユーザに対応します。user_id は後述の users_games カテゴリや game_events カテゴリの同名のカラムに対応しています。\nuser_id,opt,vote_time_unix, 7982**********9956,true,, 5090**********1841,true,, 7982**********4467,true,, 7998**********7914,true,, 7962**********0756,true,, ... カラム名 説明 user_id Discord でのユーザの ID opt データ収集にオプトインしていれば true、オプトアウトしていれば false vote_time_unix 意味なし（必ず空） users_games カテゴリ users_games カテゴリでは、そのギルドで行われた各ゲームに参加したユーザの一覧がダウンロードできます。\ngame_id で絞り込めば、そのゲームに参加したユーザの情報が一覧できますし、user_id で絞り込めば、あるユーザの通算のゲームの参加状況や勝敗を一覧できます。\n以下の例は game_id でソートしたものです。\nuser_id,guild_id,game_id,player_name,player_color,player_role,player_won 3057**********2493,8025**********6123,1,Player1,0,0,false 3466**********4338,8025**********6123,1,Player2,7,0,false 3840**********4563,8025**********6123,1,Player3,4,1,true 5090**********1842,8025**********6123,1,Player4,3,0,false 7962**********0756,8025**********6123,1,Player5,8,0,false 7982**********4468,8025**********6123,1,Player6,9,0,false 7982**********4458,8025**********6123,1,Player7,5,1,true 7982**********6161,8025**********6123,1,Player8,1,0,false 7998**********7914,8025**********6123,1,Player9,2,0,false 3057**********2493,8025**********6123,2,Player1,0,0,true 3466**********4338,8025**********6123,2,Player2,7,0,true 3840**********4563,8025**********6123,2,Player3,4,0,true 5090**********1842,8025**********6123,2,Player4,3,1,false 7962**********0756,8025**********6123,2,Player5,8,0,true 7982**********4468,8025**********6123,2,Player6,9,0,true 7982**********4458,8025**********6123,2,Player7,5,0,true 7982**********6161,8025**********6123,2,Player8,1,0,true 7998**********7914,8025**********6123,2,Player9,6,1,false ... カラム名 説明 user_id Discord でのユーザの ID guild_id Discord でのギルドの ID game_id そのゲームの一意の ID player_name そのユーザのプレイヤ名 player_color そのユーザのプレイヤの色の ID（次表参照） player_role そのユーザがクルーなら 0、インポスターなら 1 player_won そのユーザが勝利したなら true、敗北したなら false player_color 説明 0 レッド 1 ブルー 2 グリーン 3 ピンク 4 オレンジ 5 イエロー 6 ブラック 7 ホワイト 8 パープル 9 ブラウン 10 シアン 11 ライム 12 マルーン 13 ローズ 14 バナナ 15 グレー 16 タン 17 コーラル game_events カテゴリ game_events カテゴリでは、そのギルドで行われた各ゲームの詳細な時系列情報がダウンロードできます。\n1 行がゲーム中の 1 イベントに相当します。ある単一のゲームで発生したイベントを時系列で追いかけたい場合、当該の game_id で絞り込んで、イベント群を event_id の順に見ていくことになります。\n後述しますが、イベントにはいくつか種類があり、その種類に応じた補足情報が payload に保持されています。\nなお、ここで記録されているイベントは、あくまでゲームの内部処理に基づくものです。したがって、例えば追放イベントとそれによる死亡イベントが時間差で別のイベントとして記録されるなど、プレイヤ目線の時系列とは若干のギャップがあることがあります。このあたりは、いくつかのゲームで実際に時系列の再現を試みると雰囲気がつかめるはずです。\nevent_id,user_id,game_id,event_time,event_type,payload, 1,,1,1611451395,2,1, 2,,1,1611451412,2,2, 3,3466**********4338,1,1611451498,3,{\u0026#34;Name\u0026#34;: \u0026#34;Player2\u0026#34;, \u0026#34;Color\u0026#34;: 7, \u0026#34;Action\u0026#34;: 6, \u0026#34;IsDead\u0026#34;: false, \u0026#34;Disconnected\u0026#34;: false}, 4,,1,1611451498,2,1, 5,,1,1611451507,3,{\u0026#34;Name\u0026#34;: \u0026#34;Player2\u0026#34;, \u0026#34;Color\u0026#34;: 7, \u0026#34;Action\u0026#34;: 2, \u0026#34;IsDead\u0026#34;: true, \u0026#34;Disconnected\u0026#34;: false}, 6,5090**********1842,1,1611451533,3,{\u0026#34;Name\u0026#34;: \u0026#34;Player4\u0026#34;, \u0026#34;Color\u0026#34;: 3, \u0026#34;Action\u0026#34;: 2, \u0026#34;IsDead\u0026#34;: true, \u0026#34;Disconnected\u0026#34;: false}, 7,,1,1611451534,2,2, 8,,1,1611451578,2,1, 9,,1,1611451625,2,2, 10,,1,1611451703,2,1, 11,7998**********7914,1,1611451737,3,{\u0026#34;Name\u0026#34;: \u0026#34;Player9\u0026#34;, \u0026#34;Color\u0026#34;: 2, \u0026#34;Action\u0026#34;: 2, \u0026#34;IsDead\u0026#34;: true, \u0026#34;Disconnected\u0026#34;: false}, 12,,1,1611451747,2,4, 13,,1,1611451752,2,0, 14,,2,1611451813,2,1, 15,3840**********4563,2,1611451908,3,{\u0026#34;Name\u0026#34;: \u0026#34;Player3\u0026#34;, \u0026#34;Color\u0026#34;: 4, \u0026#34;Action\u0026#34;: 2, \u0026#34;IsDead\u0026#34;: true, \u0026#34;Disconnected\u0026#34;: false}, 16,3057**********2493,2,1611451943,3,{\u0026#34;Name\u0026#34;: \u0026#34;Player1\u0026#34;, \u0026#34;Color\u0026#34;: 0, \u0026#34;Action\u0026#34;: 2, \u0026#34;IsDead\u0026#34;: true, \u0026#34;Disconnected\u0026#34;: false}, 17,,2,1611451955,2,2, 18,,2,1611452124,2,1, 19,,2,1611452178,2,2, 20,,2,1611452346,2,1, 21,7982**********4468,2,1611452389,3,{\u0026#34;Name\u0026#34;: \u0026#34;Player6\u0026#34;, \u0026#34;Color\u0026#34;: 9, \u0026#34;Action\u0026#34;: 2, \u0026#34;IsDead\u0026#34;: true, \u0026#34;Disconnected\u0026#34;: false}, 22,,2,1611452392,2,2, 23,7998**********7914,2,1611452561,3,{\u0026#34;Name\u0026#34;: \u0026#34;Player9\u0026#34;, \u0026#34;Color\u0026#34;: 6, \u0026#34;Action\u0026#34;: 6, \u0026#34;IsDead\u0026#34;: false, \u0026#34;Disconnected\u0026#34;: false}, ... 37,,26,1611453125,2,0, 38,,26,1611453125,1,{\u0026#34;Map\u0026#34;: 0, \u0026#34;Region\u0026#34;: 1, \u0026#34;LobbyCode\u0026#34;: \u0026#34;TKZODF\u0026#34;}, ... カラム名 説明 event_id そのイベントの一意の ID user_id Discord でのユーザの ID game_id そのゲームの一意の ID event_time そのイベントの発生時刻（エポック秒） event_type そのイベントの種類（次表参照） payload そのイベントの補足情報（後述） event_type 説明 payload 1 ロビーの詳細情報（マップやロビーコードなど） JSON 文字列 2 フェイズの遷移（タスク開始、緊急会議開始など） フェイズの ID 3 プレイヤの状態の変化（追放、キルなど） JSON 文字列 event_type が 1 のときの payload payload 説明 Map マップの ID（次表参照） Region リージョンの ID（次々表参照） LobbyCode ロビーコード Map 説明 0 The Skeld 1 Mira HQ 2 Polus 3 dlekS ehT 4 The Airship Region 説明 0 北米 1 アジア 2 欧州 event_type が 2 のときの payload payload 説明 0 ロビーに移動 1 タスクを開始 2 緊急会議を開始 3 メニューに移動 4 ゲームオーバー event_type が 3 のときの payload payload 説明 Name プレイヤ名 Color プレイヤの色の ID（users_games カテゴリの表参照） Action プレイヤの状態変化の種類の ID（次表参照） IsDead 死亡していれば true、生存していれば false Disconnected 切断されていれば true、接続されていれば false Action 説明 0 プレイヤがロビーに参加した 1 プレイヤがゲームから離脱した 2 プレイヤが死亡した 3 プレイヤが色を変更した 4 プレイヤの情報が再読み込みされた 5 プレイヤがゲームから切断された 6 プレイヤが追放された まとめ AutoMuteUs 7.3 で追加された、データベース内のデータをダウンロードする機能を紹介しました。\nダウンロードできるデータは、実際に AutoMuteUs の戦績表示（/stats view）で分析に使われるデータそのものです。人間が読み取るにはややこしさもありますが、うまく活用できるとおもしろいかもしれません。\nAutoMuteUs 関連おすすめエントリ AutoMuteUs 7.3 の新機能： データベース内のデータのダウンロード AutoMuteUs 7.0 のリリース： スラッシュコマンド対応などいろいろ AutoMuteUs のよくある質問と回答とかいろいろ Among Us 用ボット AutoMuteUs のあまり知られていない便利な機能 ","date":"2023-01-13T18:48:05Z","image":"/archives/4940/img/image-246.jpg","permalink":"/archives/4940/","title":"AutoMuteUs 7.3 の新機能： データベース内のデータのダウンロード"},{"content":"はじめに Ansible Automation Platform（AAP）の Automation Mesh では、Execution Node と Hop Node を含んだ複雑なメッシュネットワークを構成できます。一方で AWX では、Kubernetes を使ったデプロイの場合、Hop Node を利用できないため、構成できるトポロジはスター型がせいぜいです。\nとはいえ、前回のエントリ で確認したとおり、Hop Node は単なる Receptor のノード にすぎません。また、Kubernetes ではなく Docker Compose を使った開発用の構成 であれば Hop Node を含んだデプロイも行えることからもわかるように、Hop Node を扱う機能自体は内包されています。\nそこで本エントリでは、Kuberentes 上の AWX で、Hop Node を含んだマルチホップの Automation Mesh を構築 します。技術的には Hop Node を Windows で構成する ことも可能です。\nなお、紹介している内容は公式にはサポートされていない強引なハック なので、真似する場合は自己責任でお願いします。本当は、素直に Hop Node が公式にサポートされるまで待つほうが賢明 です。\nおことわり 本エントリで紹介している内容は、本エントリ公開時点の AWX では 公式にはサポートされていない ものです。技術的にとりあえず動く状態 を 強引に実現 する ハック感が強い手順 であり、あくまで技術的な理解を深める目的なので、実環境での利用はまったくおすすめしません。\nHop Node のサポートはロードマップには含まれるようなので、実環境でしっかり利用したい場合は、素直に公式の実装を待つほうが賢明です。\nなお、本エントリは AWX の 21.10.2 を前提としています。\n今回の構成 Kubernetes 上の AWX から Hop Node を経由して Execution Node でジョブを実行できる構成を考えます。Hop Node と Execution Node はいずれも Kubernetes 外の独立した仮想マシンです。\nexec01 と exec02 には hop01 か hop02 のどちらかを経由することで、また exec03 には hop03 と hop04 を経由することでそれぞれ到達できます。\n手順 Kubernetes 上に AWX が構築されている前提で、次の流れで作業します。\nインスタンスの登録とそれぞれのピアの修正 AWX にインスタンスとして Hop Node と Execution Node の情報を登録します すべてのインスタンスのピア情報を修正します（任意） Control Node の構成 AWX 側の Receptor の設定ファイルを修正し、AWX が正しいピアと接続されるようにします Execution Node と Hop Node の構成 Execution Node と Hop Node に Receptor を導入します 動作の確認 Receptor のメッシュネットワークの状態を確認します AWX でインスタンスグループを構成し、任意の Execution Node でジョブが正常に実行できることを確認します 実際には 1 と 2 は逆のほうが手間をほんの少し（後述する Pod の再作成後のピアの再修正の分）減らせますが、流れがわかりにくくなってしまうので今回はこうしています。\nエントリ中で使っている実際のファイル群は GitHub のリポジトリに配置 しています。\nインスタンスの登録とそれぞれのピアの修正 AWX にインスタンスの情報を登録し、それぞれのピアを修正します。\nインスタンスの登録 インスタンスの登録は、Web UI や API では Execution Node しか扱えないため、今回は awx-manage コマンドを使います。\nawx-manage provision_instance --hostname \u0026lt;ホスト名\u0026gt; --node_type \u0026lt;ノードタイプ\u0026gt; \u0026lt;ノードタイプ\u0026gt; は、Execution Node であれば execution、Hop Node であれば hop です。\n今回のような Kubernetes 上の AWX では、このコマンドは実際には AWX の Pod の awx-task コンテナで叩くことになります。今回は全部で 7 ノードを登録するため、次のように kubectl exec を for で回して登録します。登録時に IP アドレス関連の警告が出ますが、無視します。\nEXEC_NODES=\u0026#34; exec01.ansible.internal exec02.ansible.internal exec03.ansible.internal \u0026#34; HOP_NODES=\u0026#34; hop01.ansible.internal hop02.ansible.internal hop03.ansible.internal hop04.ansible.internal \u0026#34; for exec in ${EXEC_NODES}; do kubectl -n awx exec -it deployment/awx -c awx-task -- awx-manage provision_instance --hostname ${exec} --node_type execution done for hop in ${HOP_NODES}; do kubectl -n awx exec -it deployment/awx -c awx-task -- awx-manage provision_instance --hostname ${hop} --node_type hop done この段階で、登録したインスタンスが Web UI の Instances のページに表示されるようになります。Node Type もコマンドで指定した通りです。\nNode Type が execution のノードは、Control Node のインスタンス（AWX）の Peers に 自動で登録 されます。これは現在の AWX では仕様であり、抑制できません。\nTopology View では、登録されているインスタンスとそのピアの情報をグラフィカルに確認できます。この段階では、Execution Node のインスタンスが Control Node のインスタンスのピアになっているだけで、Hop Node のインスタンスは孤立しています。\nピアの修正（任意） 前述のように、この段階で AWX が認識しているトポロジは、Execution Node が Control Node と直結され、Hop Node が孤立した状態です。ここで、各インスタンスのピアを修正して、冒頭の構成図の通りの正しいトポロジを定義します。\nこの手順は、現在の AWX ではあくまで画面上の表示を直す程度の意味しかなさそうで、実施は任意です。スキップしても動作に影響はなさそうですが、Web UI 上の表示と実機の状態が整合しなくなるため、できれば実施したいところです。\nピアの修正も、awx-manage コマンドで実施します。次の 3 パタンでの指定が可能です。\n# インスタンスにピアを追加する場合 awx-manage register_peers \u0026lt;接続元インスタンス\u0026gt; --peers \u0026lt;追加するピア\u0026gt; ... # インスタンスのピアを削除する場合 awx-manage register_peers \u0026lt;接続元インスタンス\u0026gt; --disconnect \u0026lt;削除するピア\u0026gt; ... # インスタンスのピアを丸ごと置き換える場合 awx-manage register_peers \u0026lt;接続元インスタンス\u0026gt; --exact \u0026lt;ピア\u0026gt; ... 今回は、次のコマンドで修正します。\nCONTROL_NODE=$(kubectl -n awx get pod --no-headers -o custom-columns=:metadata.name -l app.kubernetes.io/name=awx) kubectl -n awx exec -it deployment/awx -c awx-task -- awx-manage register_peers ${CONTROL_NODE} --exact hop01.ansible.internal hop02.ansible.internal hop03.ansible.internal kubectl -n awx exec -it deployment/awx -c awx-task -- awx-manage register_peers hop01.ansible.internal --peers exec01.ansible.internal exec02.ansible.internal kubectl -n awx exec -it deployment/awx -c awx-task -- awx-manage register_peers hop02.ansible.internal --peers exec01.ansible.internal exec02.ansible.internal kubectl -n awx exec -it deployment/awx -c awx-task -- awx-manage register_peers hop03.ansible.internal --peers hop04.ansible.internal kubectl -n awx exec -it deployment/awx -c awx-task -- awx-manage register_peers hop04.ansible.internal --peers exec03.ansible.internal Control Node のホスト名は Pod 名から拾えます。Control Node のピアには Execution Node が自動で登録されてしまっているため、ここでは --exact を使って Hop Node のみに置き換えています。\n一連の作業が完了すると、各インスタンスの Peers が修正され、Topology View も狙い通りの矢印っぷりになったことが確認できます。\nなお、AWX の Pod が再作成 されると、AWX は自分自身を新しい Control Node のインスタンスとして登録して古い Control Node を置き換えています。この影響で、Pod が再作成されるたびに Control Node のピア情報が初期化 され、Execution Node との接続が復活 してしまいます。\nしたがって、Pod の再作成が発生した後は、もし Web UI 上の表示を実機と整合させたいのであれば、前述の awx-manage register_peers コマンドの再実行が必要です（実際に後述の手順でこの状況が発生します）。\nControl Node の構成 理想を言えば、ここまでの作業によって AWX 側の Receptor の設定ファイルもきれいに書き換えられてほしい ところです。\nが、前述の通り、この周辺は現在の AWX では充分にサポートされておらず、実装も不十分なため、設定ファイルは残念ながら自力でカスタマイズする必要があります。\n前提： 現在の実装の課題 ここまでの作業で、Web UI で確認できる通り、AWX は Control Node である自分自身のピアを正しく把握できているはずです。が、現在の AWX では、残念ながらピアの情報は DB 内に保持されるだけで、Receptor の設定ファイル（/etc/receptor/receptor.conf）にまでは正しく反映されません。\n実際、ここまでの作業を終えた段階で設定ファイルを確認しても、期待する内容とは異なっています。ピアから削除したはずの Execution Node が（自動登録された時点のまま）残っており、Hop Node もありません。\n$ kubectl -n awx exec -it deployment/awx -c awx-ee -- cat /etc/receptor/receptor.conf ... - tcp-peer: address: exec01.ansible.internal:27199 tls: tlsclient - tcp-peer: address: exec02.ansible.internal:27199 tls: tlsclient - tcp-peer: address: exec03.ansible.internal:27199 tls: tlsclient このため、この機能が正式にサポートされるまでは、AWX で複雑なトポロジのメッシュネットワークを構成したい場合、AWX 用の設定ファイルはどうにかして自力でカスタマイズするしかない 状況です。\n前提： カスタマイズの方針 カスタマイズを考えるにあたっては、Kubernetes 上の AWX の Receptor の設定ファイルを取り巻く以下の実装に注意が必要です。簡単に言えば、Execution Node を追加したときにそれが Receptor の構成にも反映されるよう、awx-task コンテナが書き換えた設定ファイルを awx-ee コンテナが読めるように工夫されています。\nawx-task コンテナと awx-ee コンテナで emptyDir を共有して同一の設定ファイルを同時に参照している 設定ファイルは、awx-task コンテナの起動時に毎回生成され、さらには Execution Node を追加すると awx-task コンテナにより 追記される 設定ファイルは、awx-ee コンテナで動作している Receptor により読み取られ利用される したがって今回は、設定ファイルは awx-task コンテナでは AWX が自由に書き替えられる ようにしなければならない一方で、awx-ee コンテナでは AWX に書き換えられることのないカスタマイズ済みの中身が読める ようにしなければならないことになります。コンテナ内の設定ファイルを直接編集したとしても、awx-task コンテナにより容易に再生成・追記されてしまうため、意図した状態を保てません。また、awx-task コンテナは設定ファイルを書き込みモードで開けないとそもそも起動できません。\nこのため、非常に強引な解決策 として、ここでは コンテナごとに同じパスで実体が異なるファイルを握らせる ことを考えます。\nawx-task コンテナには、emptyDir の Volume 上のファイルを読み書きさせる awx-ee コンテナには、カスタマイズした設定ファイルの ConfigMap をマウントする コンテナ間で設定ファイルの内容に差異が生じる ことになりますが、動作には致命的な影響はないため許容します。\n設定ファイルの用意 awx-ee コンテナに使わせたい設定ファイルを用意します。awx-task コンテナの設定ファイルは AWX が勝手に作るため用意は不要です。\n用意した設定ファイル（receptor.conf）を抜粋します。完全なモノは GitHub のリポジトリに配置 しています。\n... - node: firewallrules: - action: reject tonode: /awx-[0-9a-z]{10}-[0-9a-z]{5}/ toservice: control - control-service: ... - work-command: ... - work-signing: ... - work-kubernetes: ... - work-kubernetes: ... - tls-client: ... - tcp-peer: address: hop01.ansible.internal:27199 tls: tlsclient - tcp-peer: address: hop02.ansible.internal:27199 tls: tlsclient - tcp-peer: address: hop03.ansible.internal:27199 tls: tlsclient やや複雑に見えますが、デフォルトの設定ファイルから変更した点は以下の二つだけです。\nfirewallrules の tonode をホスト名から正規表現に変更（ノード ID が Pod の再作成ごとに変化するため） tcp-peer で目的の構成のピアを追加 これを ConfigMap にします。\n$ kubectl -n awx create configmap awx-custom-receptor-config --from-file=receptor.conf configmap/awx-custom-receptor-config created $ kubectl -n awx get configmap awx-custom-receptor-config -o yaml apiVersion: v1 data: receptor.conf: | ... - node: firewallrules: - action: reject tonode: /awx-[0-9a-z]{10}-[0-9a-z]{5}/ toservice: control ... - tcp-peer: address: hop01.ansible.internal:27199 tls: tlsclient - tcp-peer: address: hop02.ansible.internal:27199 tls: tlsclient - tcp-peer: address: hop03.ansible.internal:27199 tls: tlsclient ... AWX の修正 前述のカスタマイズ方針に従って、AWX リソースの spec に extra_volumes と *_extra_volume_mounts を追加します。awx-task コンテナには emptyDir を、awx-ee コンテナには ConfigMap をマウントさせています。\n--- apiVersion: awx.ansible.com/v1beta1 kind: AWX metadata: name: awx spec: ... extra_volumes: | - name: awx-dummy-receptor-config emptyDir: {} - name: awx-custom-receptor-config configMap: name: awx-custom-receptor-config task_extra_volume_mounts: | - name: awx-dummy-receptor-config mountPath: /etc/receptor ee_extra_volume_mounts: | - name: awx-custom-receptor-config mountPath: /etc/receptor/receptor.conf subPath: receptor.conf これを任意の手段（kubectl の edit でも apply でも helm でも）で適用し、AWX Operator によるリコンサイルが終われば、それぞれのコンテナが目的の設定ファイルを使うようになります。\n$ kubectl -n awx exec -it deployment/awx -c awx-task -- cat /etc/receptor/receptor.conf ... - tcp-peer: address: exec01.ansible.internal:27199 tls: tlsclient - tcp-peer: address: exec02.ansible.internal:27199 tls: tlsclient - tcp-peer: address: exec03.ansible.internal:27199 tls: tlsclient $ kubectl -n awx exec -it deployment/awx -c awx-ee -- cat /etc/receptor/receptor.conf ... - tcp-peer: address: hop01.ansible.internal:27199 tls: tlsclient - tcp-peer: address: hop02.ansible.internal:27199 tls: tlsclient - tcp-peer: address: hop03.ansible.internal:27199 tls: tlsclient awx-task コンテナでは AWX が生成した設定ファイルが、awx-ee コンテナではカスタマイズした設定ファイルが、それぞれ配置・参照できていることがわかります。\nピアの再修正（任意） AWX Operator が AWX リソースをリコンサイルする過程で、AWX の Pod が再作成されています。\nこれにより、前述の通り Control Node のピア情報が初期化されてしまっているため、気になるのであればここで Control Node 分の awx-manage register_peers を再実行します。\nCONTROL_NODE=$(kubectl -n awx get pod --no-headers -o custom-columns=:metadata.name -l app.kubernetes.io/name=awx) kubectl -n awx exec -it deployment/awx -c awx-task -- awx-manage register_peers ${CONTROL_NODE} --exact hop01.ansible.internal hop02.ansible.internal hop03.ansible.internal Execution Node と Hop Node の構成 Control Node（AWX）ができたら、Execution Node と Hop Node を整えていきます。\nExecution Node の構成 Execution Node の構成には、AWX の標準機能をそのまま使えます。すなわち、以前のエントリ で紹介したように、AWX が生成してくれるインストールバンドルを素直にそのまま利用すれば完了です。\n詳細は 以前のエントリ で紹介しているため、本エントリでは割愛します。\nHop Node の構成 現在の AWX では Hop Node 用のインストールバンドルは生成できないため、何らかの形で手動で導入が必要です。\n今回は、Execution Node のインストールバンドルで利用されている ansible.receptor コレクションの ansible.receptor.setup ロール を使って構成します。また、Control Node と Execution Node がデフォルトで TLS を利用する形で構成されるため、Hop Node もこれに倣うものとします。\n……が、現在の ansible.receptor.setup には設定ファイルの *-peer が正しく生成されないバグ（Issue、PR）があるため、手元で試す場合は PR の修正を手動で反映させるか、PR 元のブランチ からインストールしてください。\nさて、まずは、Hop Node が利用する証明書を生成する必要があります。証明書は、以前のエントリ で紹介した通り receptor コマンドで生成できます。一点、CA 証明書の秘密鍵 が awx-web コンテナ にしかないため、一時的に awx-ee コンテナにコピーする必要がある点は注意が必要です。\nHOP_NODES=\u0026#34; hop01.ansible.internal hop02.ansible.internal hop03.ansible.internal hop04.ansible.internal \u0026#34; # CA 証明書の秘密鍵を awx-web から awx-ee にコピー kubectl -n awx exec deployment/awx -c awx-web -- cat /etc/receptor/tls/ca/receptor-ca.key | kubectl -n awx exec -i deployment/awx -c awx-ee -- bash -c \u0026#34;cat \u0026gt; /tmp/receptor-ca.key\u0026#34; # Hop Node の証明書と秘密鍵を生成してローカルのカレントディレクトリにコピー for node in ${HOP_NODES}; do kubectl -n awx exec deployment/awx -c awx-ee -- mkdir -p /tmp/${node} kubectl -n awx exec deployment/awx -c awx-ee -- receptor --cert-makereq bits=2048 commonname=${node} dnsname=${node} nodeid=${node} outreq=/tmp/${node}/receptor.csr outkey=/tmp/${node}/receptor.key kubectl -n awx exec deployment/awx -c awx-ee -- receptor --cert-signreq req=/tmp/${node}/receptor.csr cacert=/etc/receptor/tls/ca/receptor-ca.crt cakey=/tmp/receptor-ca.key outcert=/tmp/${node}/receptor.crt verify=true mkdir -p ${node} kubectl -n awx exec deployment/awx -c awx-ee -- bash -c \u0026#34;tar zcf - /tmp/${node}\u0026#34; | tar zxvf - --strip-components 2 -C ${node} kubectl -n awx exec deployment/awx -c awx-ee -- cat /etc/receptor/tls/ca/receptor-ca.crt \u0026gt; ${node}/receptor-ca.crt done # awx-ee にコピーした CA 証明書の秘密鍵を削除 kubectl -n awx exec deployment/awx -c awx-ee -- rm /tmp/receptor-ca.key これで、ローカルのカレントディレクトリに必要なファイル群が配置されます。\n$ tree hop* hop01.ansible.internal ├── receptor-ca.crt ├── receptor.crt ├── receptor.csr └── receptor.key hop02.ansible.internal ├── receptor-ca.crt ├── receptor.crt ├── receptor.csr └── receptor.key hop03.ansible.internal ├── receptor-ca.crt ├── receptor.crt ├── receptor.csr └── receptor.key hop04.ansible.internal ├── receptor-ca.crt ├── receptor.crt ├── receptor.csr └── receptor.key 0 directories, 16 files 続けて、ansible.receptor.setup ロールを使うプレイブックを作成します。\n--- - hosts: all become: true tasks: - ansible.builtin.user: name: \u0026#34;{{ receptor_user }}\u0026#34; shell: /bin/bash - ansible.posix.firewalld: port: \u0026#34;{{ receptor_port }}/tcp\u0026#34; permanent: yes state: enabled - community.general.copr: name: ansible-awx/receptor state: enabled - ansible.builtin.import_role: name: ansible.receptor.setup - ansible.builtin.systemd: name: receptor state: restarted あとは、ansible.receptor.setup ロールがよしなにやってくれるようにインベントリで必要な変数を定義して、プレイブックを実行するだけです。Hop Node なので、tcp-peer と tcp-listener が適切に記述され、ワークタイプの定義を含まない設定ファイルができれば充分です。\n--- all: hosts: hop01.ansible.internal: ansible_host: hop01.ansible.internal receptor_peers: - protocol: tcp address: exec01.ansible.internal port: 27199 - protocol: tcp address: exec02.ansible.internal port: 27199 hop02.ansible.internal: ansible_host: hop02.ansible.internal receptor_peers: - protocol: tcp address: exec01.ansible.internal port: 27199 - protocol: tcp address: exec02.ansible.internal port: 27199 hop03.ansible.internal: ansible_host: hop03.ansible.internal receptor_peers: - protocol: tcp address: hop04.ansible.internal port: 27199 hop04.ansible.internal: ansible_host: hop04.ansible.internal receptor_peers: - protocol: tcp address: exec03.ansible.internal port: 27199 vars: ansible_user: root ansible_ssh_private_key_file: ~/.ssh/id_rsa receptor_user: awx receptor_group: awx receptor_tls: true custom_tls_certfile: \u0026#34;{{ (ansible_host, \u0026#39;receptor.crt\u0026#39;) | path_join }}\u0026#34; custom_tls_keyfile: \u0026#34;{{ (ansible_host, \u0026#39;receptor.key\u0026#39;) | path_join }}\u0026#34; custom_ca_certfile: \u0026#34;{{ (ansible_host, \u0026#39;receptor-ca.crt\u0026#39;) | path_join }}\u0026#34; receptor_protocol: tcp receptor_listener: true receptor_port: 27199 ansible-playbook -i inventory.yaml install_hop_nodes.yaml 無事にプレイブックが完走すれば、Hop Node に狙い通りの設定ファイルが配置され、サービスが起動されます。\n$ ssh root@hop01.ansible.internal -- cat /etc/receptor/receptor.conf --- - node: id: hop01.ansible.internal ... - tcp-peer: address: exec01.ansible.internal:27199 redial: true tls: tls_client - tcp-peer: address: exec02.ansible.internal:27199 redial: true tls: tls_client $ ssh root@hop01.ansible.internal -- systemctl status receptor ● receptor.service - Receptor Loaded: loaded (/usr/lib/systemd/system/receptor.service; enabled; vendor preset: disabled) ... 動作の確認 ここまでで、構成は完了です。実際の動きを見ていきます。\n構成のレビュ すべての作業が問題なく完了していれば、AWX 上でも数分で全インスタンスが Ready になるはずです。\nTopoligy View でもすべて緑色になり、意図通りの構成ができています。\nReceptor 的なメッシュネットワークの状態は、awx-ee コンテナに receptorctl を導入すると確認できます。\nkubectl -n awx exec -it deployment/awx -c awx-ee -- pip install receptorctl status を実行すると、すべてのノードがきれいに認識できています。TLS も正しく構成され、想定通りのワークタイプも確認できます。\n$ kubectl -n awx exec -it deployment/awx -c awx-ee -- /home/runner/.local/bin/receptorctl --socket /var/run/receptor/receptor.sock status ... Connection Cost hop01.ansible.internal 1 hop02.ansible.internal 1 hop03.ansible.internal 1 Known Node Known Connections awx-8597d86894-czg7l hop01.ansible.internal: 1 hop02.ansible.internal: 1 hop03.ansible.internal: 1 exec01.ansible.internal hop01.ansible.internal: 1 hop02.ansible.internal: 1 exec02.ansible.internal hop01.ansible.internal: 1 hop02.ansible.internal: 1 exec03.ansible.internal hop04.ansible.internal: 1 hop01.ansible.internal awx-8597d86894-czg7l: 1 exec01.ansible.internal: 1 exec02.ansible.internal: 1 hop02.ansible.internal awx-8597d86894-czg7l: 1 exec01.ansible.internal: 1 exec02.ansible.internal: 1 hop03.ansible.internal awx-8597d86894-czg7l: 1 hop04.ansible.internal: 1 hop04.ansible.internal exec03.ansible.internal: 1 hop03.ansible.internal: 1 Route Via exec01.ansible.internal hop01.ansible.internal exec02.ansible.internal hop01.ansible.internal exec03.ansible.internal hop03.ansible.internal hop01.ansible.internal hop01.ansible.internal hop02.ansible.internal hop02.ansible.internal hop03.ansible.internal hop03.ansible.internal hop04.ansible.internal hop03.ansible.internal Node Service Type Last Seen Tags hop01.ansible.internal control StreamTLS 2022-12-20 01:07:48 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} hop02.ansible.internal control StreamTLS 2022-12-20 01:07:51 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} hop03.ansible.internal control StreamTLS 2022-12-20 01:07:48 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} hop04.ansible.internal control StreamTLS 2022-12-20 01:07:46 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} exec01.ansible.internal control StreamTLS 2022-12-20 01:08:10 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} exec02.ansible.internal control StreamTLS 2022-12-20 01:08:19 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} exec03.ansible.internal control StreamTLS 2022-12-20 01:08:37 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} awx-8597d86894-czg7l control Stream 2022-12-19 16:08:38 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} Node Work Types awx-8597d86894-czg7l local, kubernetes-runtime-auth, kubernetes-incluster-auth Node Secure Work Types exec01.ansible.internal ansible-runner exec02.ansible.internal ansible-runner exec03.ansible.internal ansible-runner ジョブの実行 インスタンスの状態が問題なければ、あとは通常の Execution Node と同じ感覚で使えます。以前のエントリ で紹介した通り、Instance Group を定義してインスタンスを追加し、Job Template から指定するだけです。\nAnsible Runner や Podman の動きも 以前のエントリ で紹介した通りです。特別なことは何もないので割愛します。\n補足： Windows 版 Hop Node Receptor それ自体は Windows でも動作するように実装されています。ということは、技術的には Hop Node を Windows でも構成できる ということになります。実際、Windows 版 Hop Node を介してジョブが実行できることは確認済み です。\nWindows 版のバイナリはどこでも配布されていないため、自前でのビルドは必須ですが、EXE ファイルができてしまえば起動の仕方は Linux 版と変わりありません。\n# ビルド（Linux 上で実行する場合） GOOS=windows go build -o receptor.exe ./cmd/receptor-cl # ビルド（Windows 上で実行する場合） go build -o receptor.exe .\\cmd\\receptor-cl # 起動 .\\receptor.exe -c .\\receptor.conf Windows で構成する場合の設定ファイルは、例えば次のような中身です。ファイルのパスが Windows 表記になることと、control-service でソケットファイルのパスを渡す必要がないことに注意すれば、Linux 用の設定ファイルをほぼそのまま流用できます。\n--- - node: id: hop01.ansible.internal - log-level: info - control-service: service: control tls: tls_server - tls-server: name: tls_server cert: C:\\receptor\\receptor.crt key: C:\\receptor\\receptor.key clientcas: C:\\receptor\\receptor-ca.crt requireclientcert: true mintls13: False - tls-client: name: tls_client cert: C:\\receptor\\receptor.crt key: C:\\receptor\\receptor.key rootcas: C:\\receptor\\receptor-ca.crt insecureskipverify: false mintls13: False - tcp-listener: port: 27199 tls: tls_server - tcp-peer: address: exec01.ansible.internal:27199 redial: true tls: tls_client - tcp-peer: address: exec02.ansible.internal:27199 redial: true tls: tls_client 起動できてしまえば、AWX からの見え方は Linux 版の Hop Node とまったく一緒です。\nまとめ 背景にある実装を理解できれば、強引に Hop Node を構成できることがわかりました。\n本番環境で安心して使うには公式のサポートを待った方がよいのは前述の通りですが、多少のトラブルを許容できる環境であれば、現状でも便利に使えるかもしれません。\nReceptor 関連エントリ Receptor (1)： Receptor はじめの一歩 Receptor (2)： Kubernetes 上でのワークの実行 Receptor (3)： 暗号化と認証、ファイアウォール、電子署名 Receptor (4)： AWX と Automation Mesh での Receptor の使われ方 Receptor (5)： AWX で Hop Node 込みの Automation Mesh を強引に構成する ","date":"2023-01-10T16:05:17Z","image":"/archives/4896/img/image-371.png","permalink":"/archives/4896/","title":"Receptor (5)： AWX で Hop Node 込みの Automation Mesh を強引に構成する"},{"content":"はじめに これまでの Receptor 関連のエントリ（[[1]][1]、[[2]][2]、[[3]][3]）では、Receptor 単体に注目して、その動作を紹介してきました。\n今回は、Receptor のユースケースひとつとして、AWX での Receptor の使われ方 を、共に使われている Ansible Runner の動作と共に紹介します。併せて、Automation Mesh にも触れます。\nAWX は、ジョブの実行、プロジェクトやインベントリの更新など、いくつもの処理を内部では Receptor のワークとして実行 しています。\nともすれば単なるオーバヘッドにも思えてしまうこの実装ですが、見方を変えれば、あえて Receptor を介して処理する実装 にすることで、メッシュネットワークを拡張しさえすればどのノードにもそれをオフロードできる ようになっているとも言えます。\nそして、AWX における Execution Node が、まさにその例のひとつです。詳細は本エントリで紹介しますが、技術的には Execution Node は Receptor の Executor そのものです。すなわち、AWX が参加しているメッシュネットワーク に Executor を追加して、ワーク（≒ AWX のジョブ）の送信先を変更可能に する機能と解釈できます。\nさらにこれが Ansible Automation Platform（AAP）では Automation Mesh として拡張され、Execution Node に加えて Hop Node も利用できるようになります。そしてこれも、技術的には AAP が参加する Receptor のメッシュネットワーク を ユーザが任意のノードで任意の構成にできる ようにして、ワークを任意の Executor に送れる ようにしたものです。\n前提： Ansible Runner の機能 AWX の実装を紐解くうえで、Ansible Runner の機能の理解が不可欠です。昔のエントリ でも簡単に紹介はしましたが、ここでは、後続の説明の前提として、特に次の三つの機能を紹介します。\nプレイブックの実行 リモートホストでのジョブの実行 Execution Environment（EE）の利用 以下、例で使っているファイル群は GitHub のリポジトリ に配置しています。\nプレイブックの実行 Ansible Runner を単なる Ansible のラッパとして扱う、もっとも簡単な使い方です。\n確認用に、以下の簡単なプレイブックを用意しました。\n--- - hosts: localhost gather_facts: true tasks: - ansible.builtin.debug: var: dump vars: dump: hostname: \u0026#34;{{ ansible_facts.hostname }}\u0026#34; user_dir: \u0026#34;{{ ansible_facts.user_dir }}\u0026#34; user_id: \u0026#34;{{ ansible_facts.user_id }}\u0026#34; virtualization_type: \u0026#34;{{ ansible_facts.virtualization_type }}\u0026#34; - ansible.builtin.pause: seconds: \u0026#34;{{ wait_seconds | default(30) }}\u0026#34; このプレイブックを demo.yml として次のように配置して ansible-runner コマンドを実行すると、プレイブックが実行されます。localhost をターゲットとしたプレイブックは、ansible-runner を実行したホストに対して実行されます。\ncd 07_awx/runner $ tree . . ├── inventory │ └── hosts └── project └── demo.yml 2 directories, 2 files $ ansible-runner run . -p demo.yml ... TASK [ansible.builtin.debug] *************************************************** ok: [localhost] =\u0026gt; { \u0026#34;dump\u0026#34;: { \u0026#34;hostname\u0026#34;: \u0026#34;kuro-awx01\u0026#34;, \u0026#34;user_dir\u0026#34;: \u0026#34;/home/kuro\u0026#34;, \u0026#34;user_id\u0026#34;: \u0026#34;kuro\u0026#34;, \u0026#34;virtualization_type\u0026#34;: \u0026#34;VMware\u0026#34; } } ... リモートホストでのジョブの実行 Ansible Runner は、プレイブックの実処理をリモートホストに任せられる ようにする、次のようなリモートジョブ実行機能を持っています。\nTransmit 機能 プレイブックの実行に必要な すべてのファイルと情報 を 標準出力に吐き出す 機能 Worker 機能 Transmit 機能の出力を 標準入力から受け取り、中身に従って実際に プレイブックを実行 して 結果を標準出力に吐き出す 機能 Process 機能 Worker 機能の出力を 標準入力から受け取り、中身に従って 実行結果を整形して表示 し ログを保存 する機能 Transmit、Worker、Process の 機能間の連携は標準入出力 で行われます。以下の実行例はすべて同一のノードで実行したものですが、実際には SSH 越しでも Receptor 越しでもファイル経由でも 何らかの形で標準入出力の中身さえ連携 できれば、それぞれを別のホストで処理できる ことになります。\nTransmit フェイズ プレイブックの実行に必要な一切合切 を まとめて標準出力に吐く フェイズです。\n実際に Transmit フェイズを実行し、標準出力を transmit.log として保存して中身を確認します。コマンドは先ほどの run を transmit に置き換えるだけです。\nTransmit フェイズはいわば 準備 であり、この段階では プレイブックは実行されない ため、処理はすぐに完了します。\ncd 07_awx/runner_remote $ ansible-runner transmit . -p demo.yml | tee transmit.log {\u0026#34;kwargs\u0026#34;: {\u0026#34;ident\u0026#34;: \u0026#34;81e67a815f6d4acc95446101ab2d0e3e\u0026#34;, ... {\u0026#34;zipfile\u0026#34;: 1246} UEsDBBQAAAAIAFEplFX/yIRIMQAAAEYAAAAKAAAALmdpdGlnbm9yZdPiU...{\u0026#34;eof\u0026#34;: true} 1 行目には ansible-runner に渡した引数の情報が入ります。\n$ cat transmit.log | head -n 1 | jq { \u0026#34;kwargs\u0026#34;: { ... \u0026#34;playbook\u0026#34;: \u0026#34;demo.yml\u0026#34;, ... 3 行目の先頭から {\u0026quot;eof\u0026quot;: true} までが Base64 でエンコードされた ZIP ファイルです。2 行目には ZIP ファイルのサイズが入ります。切り出してデコードし、解凍します。\n$ grep -A 1 zipfile transmit.log | tail -n 1 | sed \u0026#39;s/{\u0026#34;eof\u0026#34;: true}//g\u0026#39; | base64 -d \u0026gt; transmit.zip $ unzip -q transmit.zip -d transmit $ ls -l transmit total 4 drwxrwxr-x. 2 kuro kuro 19 Dec 14 04:52 inventory drwxrwxr-x. 2 kuro kuro 22 Dec 14 04:52 project -rw-rw-r--. 1 kuro kuro 819 Dec 14 05:16 transmit.log ansible-runner に渡したディレクトリ（.）が丸ごと圧縮されています。今回の project 下にはプレイブックがひとつしかありませんが、コレクションやロールがあればそれも含まれることになります。\nここまでで、Ansible Runner の Transmit フェイズでは、ansible-runner に渡した 引数 と ディレクトリ丸ごと が標準出力に吐かれることが確認できました。この標準出力にはプレイブックの実行に必要な情報がすべて揃っているため、これさえ連携できれば、任意のリモートホストでプレイブックを実行できることになります。\nWorker フェイズ Transmit フェイズの出力を受け取って、実際にプレイブックを実行する フェイズです。今回は Transmit フェイズの出力を transmit.log として保存していたため、ここでは実際にこれを cat で渡して Worker フェイズを実行し、さらにその出力を worker.log として保存して中身を確認します。\nWorker フェイズは実際にプレイブックを実行するため、少し時間がかかります。\n$ cat transmit.log | ansible-runner worker | tee worker.log ... {\u0026#34;uuid\u0026#34;: \u0026#34;9ae64665-6fd2-95e0-564f-000000000006\u0026#34;, \u0026#34;counter\u0026#34;: 2, \u0026#34;stdout\u0026#34;: \u0026#34;\\r\\nPLAY [localhost] ***************************************************************\u0026#34;, ... ... {\u0026#34;uuid\u0026#34;: \u0026#34;c764303b-8118-42fc-9731-d8d60ad94ec0\u0026#34;, \u0026#34;counter\u0026#34;: 14, \u0026#34;stdout\u0026#34;: \u0026#34;\\r\\nPLAY RECAP *********************************************************************... ... {\u0026#34;zipfile\u0026#34;: 11202} UEsDBBQAAAAIALUwlVXNDA0KawYAAMARAAAHAAAAY29tbWFuZL...{\u0026#34;eof\u0026#34;: true} 出力の各行には、Ansible の実行ログが JSON 形式で確認できます。また、末尾には Transmit フェイズのように ZIP ファイルが含まれます。解凍して中身を確認します。\n$ grep -A 1 zipfile worker.log | tail -n 1 | sed \u0026#39;s/{\u0026#34;eof\u0026#34;: true}//g\u0026#39; | base64 -d \u0026gt; worker.zip $ unzip -q worker.zip -d worker $ ls -l worker total 20 -rw-------. 1 kuro kuro 4544 Dec 14 20:49 command drwxr-xr-x. 2 kuro kuro 23 Dec 14 20:49 fact_cache drwx------. 2 kuro kuro 6 Dec 14 20:50 job_events -rw-------. 1 kuro kuro 1 Dec 14 20:50 rc -rw-------. 1 kuro kuro 10 Dec 14 20:50 status -rw-rw-r--. 1 kuro kuro 0 Dec 14 20:49 stderr -rw-------. 1 kuro kuro 966 Dec 14 20:50 stdout 実行された Ansible のパラメータや、実行ログが確認できます。\n$ cat worker/command | jq { \u0026#34;command\u0026#34;: [ \u0026#34;ansible-playbook\u0026#34;, \u0026#34;-i\u0026#34;, \u0026#34;/tmp/tmppub6q_87/inventory\u0026#34;, \u0026#34;demo.yml\u0026#34; ], \u0026#34;cwd\u0026#34;: \u0026#34;/tmp/tmppub6q_87/project\u0026#34;, ... $ cat worker/stdout ... TASK [ansible.builtin.debug] *************************************************** ok: [localhost] =\u0026gt; { \u0026#34;dump\u0026#34;: { \u0026#34;hostname\u0026#34;: \u0026#34;kuro-awx01\u0026#34;, \u0026#34;user_dir\u0026#34;: \u0026#34;/home/kuro\u0026#34;, \u0026#34;user_id\u0026#34;: \u0026#34;kuro\u0026#34;, \u0026#34;virtualization_type\u0026#34;: \u0026#34;VMware\u0026#34; } } ... Process フェイズ Worker フェイズの出力を受け取って、実行結果を整理する フェイズです。Worker フェイズの出力を worker.log として保存していたため、これを cat で渡して Process フェイズを実行します。引数には、ログ一式（Artifacts）の保存先を指定します。\n$ cat worker.log | ansible-runner process ./process ... TASK [ansible.builtin.debug] *************************************************** ok: [localhost] =\u0026gt; { \u0026#34;dump\u0026#34;: { \u0026#34;hostname\u0026#34;: \u0026#34;kuro-awx01\u0026#34;, \u0026#34;user_dir\u0026#34;: \u0026#34;/home/kuro\u0026#34;, \u0026#34;user_id\u0026#34;: \u0026#34;kuro\u0026#34;, \u0026#34;virtualization_type\u0026#34;: \u0026#34;VMware\u0026#34; } } ... 見慣れた結果が返ってきましたが、あくまで Worker フェイズのログを整理して出力 しただけで、プレイブックがこの瞬間に実行されているわけではありません。\n引数で指定したディレクトリには、ansible-runner run の実行時と同じログ一式が保存されます。\n$ ls -l process/artifacts total 32 -rw-------. 1 kuro kuro 4544 Dec 14 20:49 command drwxr-xr-x. 2 kuro kuro 23 Dec 14 21:18 fact_cache drwx------. 2 kuro kuro 4096 Dec 14 21:18 job_events -rw-------. 1 kuro kuro 1 Dec 14 20:50 rc -rw-------. 1 kuro kuro 10 Dec 14 20:50 status -rw-rw-r--. 1 kuro kuro 0 Dec 14 20:49 stderr -rw-------. 1 kuro kuro 966 Dec 14 20:50 stdout 標準入出力を介して、プレイブックの実行に必要なファイルと情報をまとめる Transmit、実際にプレイブックを実行する Worker、結果を整理する Process の各フェイズを別々に実行できることが確認できました。\nフェイズ間の連携は標準入出力で行われるため、前述の通り、SSH 越しでも Receptor 越しでもファイル経由でも 何らかの形で標準入出力の中身さえ連携 できれば、それぞれを別のホストで処理できる ことになります。\nExecution Environment（EE）の利用 Ansible Runner の持つ プロセス分離 機能を使うと、プレイブックを実行するプロセスを分離、平たく言えば プレイブックをコンテナ内で実行 できます。\nそして、コンテナイメージには、AWX で利用できる任意の Execution Environment（EE）のイメージを指定 できます。すなわち、EE を使ってプレイブックを実行 できます。\n基本の動き ansible-runner run の引数に --process-isolation を与えるとプロセス分離が有効になり、デフォルトでは Podman を使ってコンテナ内でプレイブックが実行されるようになります。\nさらに、--container-image で コンテナイメージを指定 できます。ここでは AWX のデフォルトの EE である quay.io/ansible/awx-ee:latest を指定しています（このイメージは AWX 用にしっかりカスタマイズされているので Ansible Runner で使うには --user=0 を Podman に渡す必要があります、自前で Ansible Builder でビルドしたイメージであれば不要です）。\ncd 07_awx/runner_ee $ ansible-runner run . -p demo.yml \\ --process-isolation \\ --container-image quay.io/ansible/awx-ee:latest \\ --container-option=\u0026#34;--user=root\u0026#34; ... TASK [ansible.builtin.debug] *************************************************** ok: [localhost] =\u0026gt; { \u0026#34;dump\u0026#34;: { \u0026#34;hostname\u0026#34;: \u0026#34;a02f803a5615\u0026#34;, \u0026#34;user_dir\u0026#34;: \u0026#34;/root\u0026#34;, \u0026#34;user_id\u0026#34;: \u0026#34;root\u0026#34;, \u0026#34;virtualization_type\u0026#34;: \u0026#34;container\u0026#34; } } ... 実行したものは先の例と同じ localhost をターゲットとしたプレイブックですが、実行結果から、今回は コンテナ内で実行 された様子がうかがえます。\npodman コマンドで確認すると、ansible-runner コマンドの実行中にコンテナが起動し、ansible-playbook が実行されていることが観察できます。\n$ podman ps CONTAINER ID IMAGE COMMAND ... a02f803a5615 quay.io/ansible/awx-ee:latest ansible-playbook ... $ podman inspect a02f803a5615 ... \u0026#34;Config\u0026#34;: { ... \u0026#34;Cmd\u0026#34;: [ \u0026#34;ansible-playbook\u0026#34;, \u0026#34;-i\u0026#34;, \u0026#34;/runner/inventory/hosts\u0026#34;, \u0026#34;demo.yml\u0026#34; ], \u0026#34;Image\u0026#34;: \u0026#34;quay.io/ansible/awx-ee:latest\u0026#34;, ... プライベートコンテナレジストリの利用 引数や設定ファイルを工夫することで、認証が必要なプライベートコンテナレジストリ上の EE のイメージも利用できます。次のような設定ファイルを env/settings として配置します。\n--- process_isolation: true container_image: registry.example.com/ansible/ee:2.12-custom container_auth_data: host: registry.example.com username: reguser password: Registry123! verify_ssl: false ここでは、利用するコンテナイメージを registry.example.com/ansible/ee:2.12-custom に変更しています。このコンテナレジストリ registry.example.com は 認証が必要 で、かつエンドポイントが 自己署名証明書を利用した HTTPS のため、container_auth_data で 認証情報と SSL の検証の無効化を指定 しています。\nこの状態で ansible-runner コマンドを実行すると、指定した認証情報を使ってイメージがプルされ、プレイブックが実行されます。\n$ ansible-runner run . -p demo.yml ... TASK [ansible.builtin.debug] *************************************************** ok: [localhost] =\u0026gt; { \u0026#34;dump\u0026#34;: { \u0026#34;hostname\u0026#34;: \u0026#34;50e720f8ae7b\u0026#34;, \u0026#34;user_dir\u0026#34;: \u0026#34;/root\u0026#34;, \u0026#34;user_id\u0026#34;: \u0026#34;root\u0026#34;, \u0026#34;virtualization_type\u0026#34;: \u0026#34;container\u0026#34; } } ... $ podman events --filter event=pull --since 1h ... 2022-12-14 22:00:21.372323245 +0000 UTC image pull registry.example.com/ansible/ee:2.12-custom ... $ podman ps CONTAINER ID IMAGE COMMAND ... 50e720f8ae7b registry.example.com/ansible/ee:2.12-custom ansible-playbook ... Ansible Runner 経由ではイメージのプルが行えましたが、Podman 全体の設定は特にいじっていないので、このレジストリに改めて手動でアクセスしようとしてもハネられてしまいます。\n$ podman pull registry.example.com/ansible/ee:2.12-custom Trying to pull registry.example.com/ansible/ee:2.12-custom... Error: initializing source docker://registry.example.com/ansible/ee:2.12-custom: pinging container registry registry.example.com: Get \u0026#34;https://registry.example.com/v2/\u0026#34;: x509: certificate signed by unknown authority このことから、Ansible Runner が container_auth_data の内容に基づいて Podman をハンドリングしていることが伺えます。\n実装として、Ansible Runner は、認証情報（container_auth_data）が指定されていれば、認証用の一時ファイルを作成 し Podman でコンテナを起動（podman run）する際に 引数 --authfile として指定 しています（以下の出力例には改行を加えています）。\n$ ps -ef | grep \u0026#34;podman run\u0026#34; kuro 10964 10963 5 22:54 pts/1 00:00:08 /usr/bin/podman run ... --authfile=/tmp/ansible_runner_registry_668a4418-dafa-447b-9e76-e5adced33e6f_6x83947w/auth.json ... registry.example.com/ansible/ee:2.12-custom ansible-playbook -i /runner/inventory/hosts demo.yml このファイルには、Ansible Runner に container_auth_data で指定した認証情報が含まれています。\n$ cat /tmp/ansible_runner_registry_668a4418-dafa-447b-9e76-e5adced33e6f_6x83947w/auth.json { \u0026#34;auths\u0026#34;: { \u0026#34;registry.example.com\u0026#34;: { \u0026#34;auth\u0026#34;: \u0026#34;cmVndXNlcjpSZWdpc3RyeTEyMyE=\u0026#34; } } } $ cat /tmp/ansible_runner_registry_668a4418-dafa-447b-9e76-e5adced33e6f_6x83947w/auth.json \\ | jq -r \u0026#39;.auths.\u0026#34;registry.example.com\u0026#34;.auth\u0026#39; | base64 -d reguser:Registry123! さらに、container_auth_data で verify_ssl が false に指定されたレジストリがある場合、Ansible Runner は、設定用の一時ファイル を作成し、podman run のプロセスに 環境変数 CONTAINERS_REGISTRIES_CONF と REGISTRIES_CONFIG_PATH として渡し ます（二つあるのは Podman の 3.1.0 未満と以降のバリエーションに対応するためです）。\n$ ps -ef | grep \u0026#34;podman run\u0026#34; kuro 10964 10963 5 22:54 pts/1 00:00:08 ... $ cat /proc/10964/environ --show-nonprinting | sed \u0026#39;s/\\^@/\\n/g\u0026#39; | grep REGISTRIES CONTAINERS_REGISTRIES_CONF=/tmp/ansible_runner_registry_668a4418-dafa-447b-9e76-e5adced33e6f_6x83947w/registries.conf REGISTRIES_CONFIG_PATH=/tmp/ansible_runner_registry_668a4418-dafa-447b-9e76-e5adced33e6f_6x83947w/registries.conf このファイルには、container_auth_data で verify_ssl を false にしたレジストリが insecure = true として含まれています。\n$ cat /tmp/ansible_runner_registry_668a4418-dafa-447b-9e76-e5adced33e6f_6x83947w/registries.conf [[registry]] location = \u0026#34;registry.example.com\u0026#34; insecure = true Ansible Runner が container_auth_data の内容に基づいて Podman に一時的に設定ファイル群を与える ことで、Podman がプライベートレジストリからのイメージをプルできるようにうまくハンドリングしている様子が確認できました。ユーザはあくまで Ansible Runner に設定を与えたのみ であり、 Podman の設定には直接は全く触れていない 点がポイントです。\nリモートジョブ実行機能との併用 前述したリモートジョブ実行機能とももちろん併用できます。この場合、実際にコンテナが起動するのは Worker フェイズのみです。\nWorker フェイズの出力からファイルを抽出すると、コンテナや EE 関連の情報（Ansible のバージョンや利用可能なコレクション、Podman を起動するパラメータなど）も確認できます。\n$ ansible-runner transmit . -p demo.yml | ansible-runner worker | tee worker.log ... $ grep -A 1 zipfile worker.log | tail -n 1 | sed \u0026#39;s/{\u0026#34;eof\u0026#34;: true}//g\u0026#39; | base64 -d \u0026gt; worker.zip $ unzip -q worker.zip -d worker $ cat worker/ansible_version.txt ansible [core 2.12.5.post0] $ cat worker/collections.json | jq { \u0026#34;/usr/share/ansible/collections/ansible_collections\u0026#34;: { \u0026#34;community.general\u0026#34;: { \u0026#34;version\u0026#34;: \u0026#34;6.0.0\u0026#34; ... $ cat worker/command | jq { \u0026#34;command\u0026#34;: [ \u0026#34;podman\u0026#34;, \u0026#34;run\u0026#34;, ... \u0026#34;--authfile=/tmp/ansible_runner_registry_30044782759d4ba9a5f8d787ce571cb0_2ju3l7gd/auth.json\u0026#34;, ... \u0026#34;registry.example.com/ansible/ee:2.12-custom\u0026#34;, \u0026#34;ansible-playbook\u0026#34;, \u0026#34;-i\u0026#34;, \u0026#34;/runner/inventory/hosts\u0026#34;, \u0026#34;demo.yml\u0026#34; ], ... \u0026#34;env\u0026#34;: { ... \u0026#34;CONTAINERS_REGISTRIES_CONF\u0026#34;: \u0026#34;/tmp/ansible_runner_registry_30044782759d4ba9a5f8d787ce571cb0_2ju3l7gd/registries.conf\u0026#34;, \u0026#34;REGISTRIES_CONFIG_PATH\u0026#34;: \u0026#34;/tmp/ansible_runner_registry_30044782759d4ba9a5f8d787ce571cb0_2ju3l7gd/registries.conf\u0026#34; } } command ファイルからは、前述の --authfile オプションや環境変数（CONTAINERS_REGISTRIES_CONF と REGISTRIES_CONFIG_PATH）が実際に追加されている様子も容易に確認できます。\nAWX と Receptor 本題の、AWX における Receptor の使われ方を確認します。なお、ここで紹介するものは AWX の 21.10.1 のものです。\n全体像 ひとことでいえば、AWX はさまざまなシーンで Receptor のワーク として Ansible Runner の Worker フェイズを実行 しています。以下、AWX での ジョブの実行 を例に、まずは全体像を確認します。\nContainer Group でのジョブの実行 AWX から Container Group でジョブを実行すると、Kubernetes クラスタ上に EE の Pod が作成されて処理されます。\nここでは、Receptor の Kubernetes ワーク として Ansible Runner の Worker フェイズ が実行されています。\nAWX は Ansible Runner の Transmit フェイズを実行し、その出力を Kubernetes ワークのペイロード として渡しています。Kubernetes ワークは Executor としての自分自身 に送信されており、実行された Worker フェイズの出力を受け取ってジョブの実行結果として処理しています（ただし、結果の処理には Ansible Runner の Process フェイズは使われていません）。\nこのとき、Pod の作成は Receptor の責任範囲 であり、Ansible Runner は単にプレイブックを実行するだけ です。したがって、AWX が実行する Ansible Runner の Transmit フェイズでは、プロセス分離は無効 な設定で実行されます。\nInstance Group でのジョブの実行 AWX から Instance Group でジョブを実行すると、Execution Node 上の Podman で EE のコンテナが作成されて処理されます。\nここでは、Receptor のコマンドワーク として Ansible Runner の Worker フェイズ が実行されています。\nAWX が実行した Ansible Runner の Transmit フェイズの出力は、Executor としての Execution Node に コマンドワークのペイロード として渡されます。\nこの場合は、コンテナの作成は Ansible Runner の責任範囲 です。したがって、AWX が実行する Ansible Runner の Transmit フェイズでは、プロセス分離****が有効 な設定で実行されます。\n実行結果の処理は Container Group の場合と同じです。\nReceptor の設定内容 実際の Receptor の設定を確認します。\nAWX の Receptor の設定 AWX では、Receptor のプロセスは AWX の Pod の awx-ee コンテナで動作 しています。AWX 側の Receptor は、Controller 兼 Executor として構成されています。\n初期状態では、次の設定ファイルです（改行のみ整形しています）。これまでの Receptor 関連のエントリ（[[1]][1]、[[2]][2]、[[3]][3]）で紹介した記述ばかりです。\n- local-only: null - log-level: debug - node: firewallrules: - action: reject tonode: awx-bdfc84dd7-wp2b6 toservice: control - control-service: filename: /var/run/receptor/receptor.sock permissions: \u0026#39;0660\u0026#39; service: control - work-command: allowruntimeparams: true command: ansible-runner params: worker worktype: local - work-signing: privatekey: /etc/receptor/signing/work-private-key.pem tokenexpiration: 1m - work-kubernetes: allowruntimeauth: true allowruntimeparams: true allowruntimepod: true authmethod: runtime worktype: kubernetes-runtime-auth - work-kubernetes: allowruntimeauth: true allowruntimeparams: true allowruntimepod: true authmethod: incluster worktype: kubernetes-incluster-auth - tls-client: cert: /etc/receptor/tls/receptor.crt key: /etc/receptor/tls/receptor.key name: tlsclient rootcas: /etc/receptor/tls/ca/receptor-ca.crt ノードの id が指定されていないため、Receptor としてのノード ID には自身のホスト名が設定されます。\nController として control-service が定義されており、ソケットファイルのパスが指定されています。\nまた、Executor のワークタイプとして コマンドワークの local、Kubernetes ワークの kubernetes-runtime-auth と kubernetes-incluster-auth が定義されています。\nコマンドワーク local は ansible-runner を 自身で直接実行 するものです。params のとおり、Worker フェイズ の実行を前提としています。\nKubernetes ワークは authmethod の違いで二種類あり、kubernetes-runtime-auth は主に 他の Kubernetes クラスタ に対して Pod を作成する用途、kubernetes-incluster-auth は 自身が動作する Kubernetes クラスタ で Pod を作成する用途のワークタイプです。\nKubernetes ワークはいずれも allowruntimepod が true に設定されており、具体的な Pod の仕様は実行時に渡されることがわかります。また、ワークの電子署名用の work-signing と、TLS クライアント（tls-client）も構成されています。\nさらに、AWX に Execution Node を追加すると、追加したノードごとに次の 3 行が設定ファイルに追加されます（以下は exec01.ansible.internal をポート番号 27199 で追加した例です）。\n- tcp-peer: address: exec01.ansible.internal:27199 tls: tlsclient なお、細かくなりすぎるので詳しい紹介は省きますが、Control Service に利用されているソケットファイルは awx-task コンテナとも共有されており、実際に receptorctl（CLI ツールではなく Python モジュール版）での操作を行っているのは awx-task コンテナです。前述の Ansible Runner の Transmit フェイズも、awx-task 側で行われています。\nExecution Node の Receptor の設定 AWX に Execution Node を追加すると、これが Executor として Receptor のメッシュネットワークに参加します（Controller としての機能も持たせられているようです）。\nExecution Node には、初期状態で次の設定ファイルが配置されます。以下は exec01.ansible.internal の例です（改行のみ整形しています）。\n--- - node: id: exec01.ansible.internal - work-verification: publickey: /etc/receptor/work_public_key.pem - log-level: info - control-service: service: control filename: /var/run/receptor/receptor.sock permissions: 0660 tls: tls_server - tls-server: name: tls_server cert: /etc/receptor/tls/exec01.ansible.internal.crt key: /etc/receptor/tls/exec01.ansible.internal.key clientcas: /etc/receptor/tls/ca/mesh-CA.crt requireclientcert: true mintls13: False - tls-client: name: tls_client cert: /etc/receptor/tls/exec01.ansible.internal.crt key: /etc/receptor/tls/exec01.ansible.internal.key rootcas: /etc/receptor/tls/ca/mesh-CA.crt insecureskipverify: false mintls13: False - tcp-listener: port: 27199 tls: tls_server - work-command: worktype: ansible-runner command: ansible-runner params: worker allowruntimeparams: True verifysignature: True ワークタイプとして Execution Node で ansible-runner を実行する コマンドワーク ansible-runner が定義されています。params のとおり、Worker フェイズ の実行を前提としています。\nこのほか、Controller である AWX 側と接続するための TLS 関連や、電子署名を検証するための鍵などが構成されています。\n実際の動き 実際に AWX 側から操作を行い、Receptor や Ansible Runner の使われ方を確認します。あらかじめ、AWX 側ではジョブテンプレートとしてこれまでと同じプレイブックを実行できるように構成しています。\nContainer Group でのジョブの実行とインベントリの同期（In-Cluster 認証） AWX では、ジョブの実行やインベントリの同期は、それを実行するインスタンスが Container Group の場合、Kubernetes クラスタに EE の Pod が作成されて処理されます。すなわち、Kubernetes ワーク です。\nまずは In-Cluster 認証 を用いた Kubernetes ワークの動きを追いかけるために、デフォルトの Container Group を指定してジョブを実行します。コンテナレジストリへの認証も確認するため、ジョブテンプレートで使う EE をプライベートコンテナレジストリ上のものに構成しています。\n初回のエントリ で紹介したように、Receptor では、ワークで取り扱う標準入出力はすべて一時ファイルで保存されます。ジョブの実行中 に awx-ee コンテナの一時ファイルを見れば、ワークの動きを観察できます。ジョブの実行中に次のコマンドで一時ファイル群をホスト側にコピーします。\ncd 07_awx/awx $ kubectl -n awx exec deployment/awx -c awx-ee -- bash -c \u0026#34;tar zcf - /tmp/receptor/*/*\u0026#34; \\ | tar zxvf - --strip-components 4 -C cg_incluster tmp/receptor/awx-bdfc84dd7-wp2b6/eq4pkWHs/status.lock tmp/receptor/awx-bdfc84dd7-wp2b6/eq4pkWHs/status tmp/receptor/awx-bdfc84dd7-wp2b6/eq4pkWHs/stdin tmp/receptor/awx-bdfc84dd7-wp2b6/eq4pkWHs/stdout Receptor のワークに指定されたパラメータ を確認するため、status ファイル を確認します。\n$ cat cg_incluster/status | jq { ... \u0026#34;WorkType\u0026#34;: \u0026#34;kubernetes-incluster-auth\u0026#34;, \u0026#34;ExtraData\u0026#34;: { ... \u0026#34;KubePod\u0026#34;: \u0026#34;---\\napiVersion: v1\\n...\u0026#34;, ... } } $ cat cg_incluster/status | jq -r \u0026#39;.ExtraData.KubePod\u0026#39; --- apiVersion: v1 kind: Pod ... spec: ... containers: - args: - ansible-runner - worker - --private-data-dir=/runner image: registry.example.com/ansible/ee:2.12-custom imagePullPolicy: IfNotPresent name: worker ... imagePullSecrets: - name: automation-1568d-image-pull-secret-3 serviceAccountName: default status ファイルから、Kubernetes ワークタイプの kubernetes-incluster-auth が実行されている様子が確認できます。また、ExtraData.KubePod で Pod の仕様が指定されており、ansible-runner の Worker フェイズのコマンドが指定されていること、プライベートコンテナレジストリ上のイメージが指定されていること、そのために imagePullSecrets が指定されていることが確認できます。ansible-runner worker の --private-data-dir オプションは Transmit フェイズから渡された ZIP ファイルを展開するパスの指定です。\nimagePullSecrets は、指定された EE に認証情報が設定されている場合に AWX により自動で作成されます。\n$ kubectl -n awx get secret automation-1568d-image-pull-secret-3 -o yaml apiVersion: v1 data: .dockerconfigjson: ewogICAgImF1dGhzIjogewogICAgICAgICJyZWdpc3RyeS5leGF... kind: Secret ... type: kubernetes.io/dockerconfigjson $ kubectl -n awx get secret automation-1568d-image-pull-secret-3 -o jsonpath=\u0026#39;{$.data.\\.dockerconfigjson}\u0026#39; | base64 -d { \u0026#34;auths\u0026#34;: { \u0026#34;registry.example.com\u0026#34;: { \u0026#34;auth\u0026#34;: \u0026#34;cmVndXNlcjpSZWdpc3RyeTEyMyE=\u0026#34; } } } $ kubectl -n awx get secret automation-1568d-image-pull-secret-3 -o jsonpath=\u0026#39;{$.data.\\.dockerconfigjson}\u0026#39; | base64 -d \\ | jq -r \u0026#39;.auths.\u0026#34;registry.example.com\u0026#34;.auth\u0026#39; | base64 -d reguser:Registry123! 続けて、stdin ファイル の中身を確認します。これはつまり、AWX 側の Ansible Runner の Transmit フェイズで生成されたデータ です。Receptor を通じて、Pod で動作する Ansible Runner の Worker プロセスに渡されます。\n$ cat cg_incluster/stdin {\u0026#34;kwargs\u0026#34;: {\u0026#34;ident\u0026#34;: 19, \u0026#34;playbook\u0026#34;: \u0026#34;demo.yml\u0026#34;, ... {\u0026#34;zipfile\u0026#34;: 1839} UEsDBBQAAAAAABCnjlUAAAAAAAAAAAAAAAAKAAAAaW52ZW50b3J5...{\u0026#34;eof\u0026#34;: true} $ cat cg_incluster/stdin | head -n 1 | jq { \u0026#34;kwargs\u0026#34;: { ... \u0026#34;playbook\u0026#34;: \u0026#34;demo.yml\u0026#34;, \u0026#34;inventory\u0026#34;: \u0026#34;inventory/hosts\u0026#34;, \u0026#34;passwords\u0026#34;: { ... \u0026#34;sudo password.*:\\\\s*?$\u0026#34;: \u0026#34;Root123!\u0026#34;, ... \u0026#34;SSH password:\\\\s*?$\u0026#34;: \u0026#34;User123!\u0026#34;, ... }, \u0026#34;suppress_env_files\u0026#34;: true, \u0026#34;envvars\u0026#34;: { ... \u0026#34;ANSIBLE_SSH_CONTROL_PATH_DIR\u0026#34;: \u0026#34;/runner/cp\u0026#34;, \u0026#34;ANSIBLE_COLLECTIONS_PATHS\u0026#34;: \u0026#34;/runner/requirements_collections:~/.ansible/collections:/usr/share/ansible/collections\u0026#34;, \u0026#34;ANSIBLE_ROLES_PATH\u0026#34;: \u0026#34;/runner/requirements_roles:~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles\u0026#34; }, ... stdin ファイルの中身は、冒頭で紹介した Ansible Runner の Transmit フェイズの出力そのままです。\nAWX は Ansible Runner をその API を通じて実行しているため、CLI（ansible-runner コマンド）よりも細かな制御が可能です。Transmit フェイズの出力の 1 行目では、Ansible Runner の実行時のパラメータが確認でき、インベントリやプレイブックのほか、ジョブテンプレートに設定したパスワード類 も復号化されて渡されていることが確認できます。このほか、Worker フェイズで実行される Ansible の動作を制御する環境変数類 も多数指定されています。例えば、これが vCenter Server をソースとするインベントリの同期であれば、環境変数に認証情報も含まれることになります。\nTransmit フェイズの出力には、前述の通り、Base64 でエンコードされた ZIP ファイルも含まれます。\n$ grep -A 1 zipfile cg_incluster/stdin | tail -n 1 | sed \u0026#39;s/{\u0026#34;eof\u0026#34;: true}//g\u0026#39; | base64 -d \u0026gt; cg_incluster/stdin.zip $ unzip -q cg_incluster/stdin.zip -d cg_incluster/stdin_ $ ls -l cg_incluster/stdin_ total 0 drwx------. 2 kuro kuro 6 Dec 14 20:56 cp drwxr-xr-x. 2 kuro kuro 54 Dec 14 20:56 env drwxr-xr-x. 2 kuro kuro 19 Dec 14 20:56 inventory drwxrwxr-x. 2 kuro kuro 22 Dec 14 20:13 project inventory ディレクトリには hosts ファイルが含まれています。これは、AWX 側で指定したインベントリを JSON 形式で出力する Python スクリプト です。\n$ python3 cg_incluster/stdin_/inventory/hosts | jq { \u0026#34;all\u0026#34;: { \u0026#34;hosts\u0026#34;: [ \u0026#34;localhost\u0026#34; ], ... }, ... \u0026#34;_meta\u0026#34;: { \u0026#34;hostvars\u0026#34;: { ... \u0026#34;localhost\u0026#34;: { \u0026#34;ansible_connection\u0026#34;: \u0026#34;local\u0026#34;, \u0026#34;ansible_python_interpreter\u0026#34;: \u0026#34;{{ ansible_playbook_python }}\u0026#34;, ... } } } } 冒頭で確認した Ansible Runner のリモートジョブ実行機能が、Receptor を通じて実行されている様子が確認できました。\nContainer Group でのジョブの実行とインベントリの同期（Runtime 認証） つづけて、Runtime 認証の動きを確認します。インスタンスとして 認証情報を指定した Container Group を指定してジョブを実行し、先ほどと同様に Receptor の一時ファイルを取得します。\n$ kubectl -n awx exec deployment/awx -c awx-ee -- bash -c \u0026#34;tar zcf - /tmp/receptor/*/*\u0026#34; \\ | tar zxvf - --strip-components 4 -C cg_runtime tmp/receptor/awx-bdfc84dd7-wp2b6/aIefUYrp/status.lock tmp/receptor/awx-bdfc84dd7-wp2b6/aIefUYrp/status tmp/receptor/awx-bdfc84dd7-wp2b6/aIefUYrp/stdin tmp/receptor/awx-bdfc84dd7-wp2b6/aIefUYrp/stdout status ファイルを確認します。\n$ cat cg_runtime/status | jq { ... \u0026#34;WorkType\u0026#34;: \u0026#34;kubernetes-runtime-auth\u0026#34;, \u0026#34;ExtraData\u0026#34;: { ... \u0026#34;KubeConfig\u0026#34;: \u0026#34;---\\napiVersion: v1\\n...\u0026#34;, ... \u0026#34;KubePod\u0026#34;: \u0026#34;---\\napiVersion: v1\\n...\u0026#34;, ... } } たしかにワークタイプ kubernetes-runtime-auth が指定されています。また、先ほどと異なり、KubeConfig が指定されています。\n$ cat cg_runtime/status | jq -r \u0026#39;.ExtraData.KubeConfig\u0026#39; --- apiVersion: v1 clusters: - cluster: insecure-skip-tls-verify: true server: https://192.168.0.219:6443 ... contexts: - context: cluster: https://192.168.0.219:6443 namespace: awx user: https://192.168.0.219:6443 ... current-context: https://192.168.0.219:6443 kind: Config preferences: {} users: - name: https://192.168.0.219:6443 user: token: eyJhbGciOiJSUzI1NiIsImtpZCI6IkI1LXotczRZN1RU... これは、AWX 側で指定した Kubernetes クラスタの認証情報に基づいて AWX が生成した KubeConfig データです。外部の Kubernetes クラスタへの認証 の動きが確認できました。\nstdin ファイルの中身は先ほどの例と変わらないため省略します。\n$ cat cg_runtime/stdin | head -n 1 | jq { \u0026#34;kwargs\u0026#34;: { ... \u0026#34;playbook\u0026#34;: \u0026#34;demo.yml\u0026#34;, \u0026#34;inventory\u0026#34;: \u0026#34;inventory/hosts\u0026#34;, \u0026#34;passwords\u0026#34;: { ... $ grep -A 1 zipfile cg_runtime/stdin | tail -n 1 | sed \u0026#39;s/{\u0026#34;eof\u0026#34;: true}//g\u0026#39; | base64 -d \u0026gt; cg_runtime/stdin.zip $ unzip -q cg_runtime/stdin.zip -d cg_runtime/stdin_ $ ls -l cg_runtime/stdin_ total 0 drwx------. 2 kuro kuro 6 Dec 14 20:54 cp drwxr-xr-x. 2 kuro kuro 54 Dec 14 20:54 env drwxr-xr-x. 2 kuro kuro 19 Dec 14 20:54 inventory drwxrwxr-x. 2 kuro kuro 22 Dec 14 20:13 project Instance Group でのジョブの実行とインベントリの同期 続けて、Execution Node を利用する場合、つまり Instance Group を指定してジョブを実行した場合 の動きを確認します。\n先ほどと同様に一時ファイルを取得します。\n$ kubectl -n awx exec deployment/awx -c awx-ee -- bash -c \u0026#34;tar zcf - /tmp/receptor/*/*\u0026#34; \\ | tar zxvf - --strip-components 4 -C ig_podman tmp/receptor/awx-bdfc84dd7-wp2b6/tPqM2J8t/status.lock tmp/receptor/awx-bdfc84dd7-wp2b6/tPqM2J8t/status tmp/receptor/awx-bdfc84dd7-wp2b6/tPqM2J8t/stdin tmp/receptor/awx-bdfc84dd7-wp2b6/tPqM2J8t/stdout status ファイルを確認します。\n$ cat ig_podman/status | jq { ... \u0026#34;WorkType\u0026#34;: \u0026#34;remote\u0026#34;, \u0026#34;ExtraData\u0026#34;: { \u0026#34;RemoteNode\u0026#34;: \u0026#34;exec01.ansible.internal\u0026#34;, \u0026#34;RemoteWorkType\u0026#34;: \u0026#34;ansible-runner\u0026#34;, \u0026#34;RemoteParams\u0026#34;: { \u0026#34;params\u0026#34;: \u0026#34;--private-data-dir=/tmp/awx_17_3jjx2zyd --delete\u0026#34; }, ... \u0026#34;SignWork\u0026#34;: true, \u0026#34;TLSClient\u0026#34;: \u0026#34;tlsclient\u0026#34;, ... } } Executor としての Execution Node に コマンドワーク ansible-runner が送信されていることがわかります。このコマンドワークは、先の設定ファイルで定義されている通り、ansible-runner worker を実行するものです。また、このパラメータとして、ZIP ファイルの展開先を示す --private-data-dir と、そのパスの展開時の初期化と終了後の削除を指示する --delete が渡されています。\n続けて、stdin ファイルの 1 行目を確認します。\n$ cat ig_podman/stdin | head -n 1 | jq { \u0026#34;kwargs\u0026#34;: { ... \u0026#34;playbook\u0026#34;: \u0026#34;demo.yml\u0026#34;, \u0026#34;inventory\u0026#34;: \u0026#34;inventory/hosts\u0026#34;, \u0026#34;passwords\u0026#34;: { ... \u0026#34;sudo password.*:\\\\s*?$\u0026#34;: \u0026#34;Root123!\u0026#34;, ... \u0026#34;SSH password:\\\\s*?$\u0026#34;: \u0026#34;User123!\u0026#34;, ... }, ... \u0026#34;envvars\u0026#34;: { ... }, ... \u0026#34;container_image\u0026#34;: \u0026#34;registry.example.com/ansible/ee:2.12-custom\u0026#34;, \u0026#34;process_isolation\u0026#34;: true, \u0026#34;process_isolation_executable\u0026#34;: \u0026#34;podman\u0026#34;, \u0026#34;container_options\u0026#34;: [ \u0026#34;--user=root\u0026#34;, ... \u0026#34;--pull=missing\u0026#34; ], \u0026#34;container_auth_data\u0026#34;: { \u0026#34;host\u0026#34;: \u0026#34;registry.example.com\u0026#34;, \u0026#34;username\u0026#34;: \u0026#34;reguser\u0026#34;, \u0026#34;password\u0026#34;: \u0026#34;Registry123!\u0026#34;, \u0026#34;verify_ssl\u0026#34;: false }, ... } } Executor Node でジョブを実行する場合には Podman のコンテナとして EE が起動しますが、このコンテナの起動は Ansible Runner の責任範囲です。したがって、Podman の動作を制御するためのパラメータも Transmit フェイズから渡されます。コンテナイメージ（container_image）のほか、コンテナレジストリの認証情報（container_auth_data）も渡されています。\nstdin ファイルに含まれる ZIP ファイルの中身はこれまでと同様です。\n$ grep -A 1 zipfile ig_podman/stdin | tail -n 1 | sed \u0026#39;s/{\u0026#34;eof\u0026#34;: true}//g\u0026#39; | base64 -d \u0026gt; ig_podman/stdin.zip $ unzip -q ig_podman/stdin.zip -d ig_podman/stdin_ $ ls -l ig_podman/stdin_ total 0 drwx------. 2 kuro kuro 6 Dec 14 20:42 cp drwxr-xr-x. 2 kuro kuro 54 Dec 14 20:42 env drwxr-xr-x. 2 kuro kuro 19 Dec 14 20:42 inventory drwxrwxr-x. 2 kuro kuro 22 Dec 14 20:13 project プロジェクトの同期 最後に、プロジェクトの同期 の動作です。AWX では、プロジェクトの同期は AWX の Pod 内で閉じて処理されます。ここでは、自分自身に対するコマンドワーク が送信されています。\n$ kubectl -n awx exec deployment/awx -c awx-ee -- bash -c \u0026#34;tar zcf - /tmp/receptor/*/*\u0026#34; \\ | tar zxvf - --strip-components 4 -C sync_pj tmp/receptor/awx-bdfc84dd7-wp2b6/YsoGXshO/status.lock tmp/receptor/awx-bdfc84dd7-wp2b6/YsoGXshO/status tmp/receptor/awx-bdfc84dd7-wp2b6/YsoGXshO/stdin tmp/receptor/awx-bdfc84dd7-wp2b6/YsoGXshO/stdout $ cat sync_pj/status | jq { ... \u0026#34;WorkType\u0026#34;: \u0026#34;local\u0026#34;, \u0026#34;ExtraData\u0026#34;: { ... \u0026#34;Params\u0026#34;: \u0026#34;worker --private-data-dir=/tmp/awx_24_80ydyxhs\u0026#34; } } $ cat sync_pj/stdin | head -n 1 | jq { \u0026#34;kwargs\u0026#34;: { ... \u0026#34;playbook\u0026#34;: \u0026#34;project_update.yml\u0026#34;, \u0026#34;inventory\u0026#34;: \u0026#34;inventory/hosts\u0026#34;, \u0026#34;passwords\u0026#34;: { ... \u0026#34;Password:\\\\s*?$\u0026#34;: \u0026#34;Git123!\u0026#34;, ... }, ... } } プロジェクトの同期を実行するプレイブック project_update.yml が実行されることがわかります。これは AWX に組み込まれたプレイブックであり、同期する対象は Ansible の変数で指定されます。\n$ grep -A 1 zipfile sync_pj/stdin | tail -n 1 | sed \u0026#39;s/{\u0026#34;eof\u0026#34;: true}//g\u0026#39; | base64 -d \u0026gt; sync_pj/stdin.zip $ unzip -q sync_pj/stdin.zip -d sync_pj/stdin_ $ ls -l sync_pj/stdin_ total 0 drwxr-xr-x. 2 kuro kuro 54 Dec 14 21:26 env drwxr-xr-x. 2 kuro kuro 19 Dec 14 21:26 inventory drwxr-xr-x. 4 kuro kuro 69 Dec 12 19:05 project $ cat sync_pj/stdin_/env/extravars ... !unsafe \u0026#39;project_path\u0026#39;: !unsafe \u0026#39;/var/lib/awx/projects/_6__demo_project\u0026#39; ... !unsafe \u0026#39;scm_url\u0026#39;: !unsafe \u0026#39;https://git:Git123%21@github.com/ansible/ansible-tower-samples\u0026#39; ... Automation Mesh と Receptor AAP の Automation Mesh における Receptor も、役割は AWX のそれと大きく変わりません。Automation Mesh とは、つまりは Receptor のメッシュネットワーク を下地に、Controller としての AAP と Executor としての Execution Node を 任意の配置で構成できる機能 と言えます。\nIn this configuration, resilient controller nodes are peered with resilient local execution nodes. Resilient local hop nodes are peered with the controller nodes. A remote execution node and a remote hop node are peered with the local hop nodes.\nChapter 3. Automation mesh design patterns - 3.5. Multi-hopped execution node\rAutomation Mesh では上図のような 複雑なトポロジ も構成できますが、これまでのエントリで紹介したように、Receptor では メッシュネットワークが組めさえすればノード間の経路はもはや気にする必要がない わけで、あえて Receptor を介する実装 にすることで、Receptor の持つ柔軟性と拡張性をそのまま AAP の強みにできている とも言えます。\nAWX にはない Hop Node（図の *_h_*）も、つまり、ワークタイプが定義されていない Receptor のノード（Relayer） なだけです。\nインストール先が OpenShift でなければ Kubernetes ワークの出番が少なくなるくらいで、親玉たる 制御プレーン が 実行先のインスタンスに応じて Ansible Runner と Receptor に渡す情報をコントロール している動きは、結局は AWX と同じです。\nまとめ AWX のさまざまな動きが、Ansible Runner と Receptor のそれぞれの仕組みをうまく使うことで実現されていることを紹介しました。AWX は、実行先のインスタンスタイプに応じて Ansible Runner や Receptor に渡す情報を制御しています。\n全体としては非常に複雑そうに見えるものの、それぞれのツールの責任範囲がはっきりしているため、踏み込んでみればそれぞれの動きは比較的シンプルであることがわかります。\nもちろんこれは偶然ではなく、こうした利用方法を見越してそれぞれのツールの開発がすすめられたものとは思いますが、それにしてもよく嚙み合っていておもしろい動きです。\nReceptor 関連エントリ Receptor (1)： Receptor はじめの一歩 Receptor (2)： Kubernetes 上でのワークの実行 Receptor (3)： 暗号化と認証、ファイアウォール、電子署名 Receptor (4)： AWX と Automation Mesh での Receptor の使われ方 Receptor (5)： AWX で Hop Node 込みの Automation Mesh を強引に構成する ","date":"2022-12-21T20:57:51Z","image":"/archives/4847/img/image-342.png","permalink":"/archives/4847/","title":"Receptor (4)： AWX と Automation Mesh での Receptor の使われ方"},{"content":"はじめに これまでのエントリ（前々回、前回）では、Receptor の基本的な動きと、Kubernetes との連携を紹介しました。\n本エントリでは、Receptor の持つ以下のセキュリティ関連の機能を取り扱います。\n接続できるピアの制限 TLS による暗号化と認証 ファイアウォール ワークの電子署名 必要なファイルは、これまで通り GitHub のリポジトリに配置 しています。\n構成例 今回は、前述のセキュリティ関連機能をさまざまなパタンで試すため、少し複雑ですが、図の 8 ノードで構成します。Controller が 4 ノード、Executor が 3 ノードです。\n一連のセキュリティ関連の機能の確認のみを目的としているため、作りこみはせずにすべてコンテナ環境で構成します。Kubernetes ではなく Docker を利用しますが、Kubernetes を利用する場合も考え方はまったく一緒です。\nなお、GitHub のリポジトリのファイル群 には、デモ用の構成をあらかじめすべて記述 してあります。このため、そもそもあるノードがメッシュネットワークに参加できなかったり、あるノードからあるノードに接続できなかったりしますが、デモのために意図的にそうしたものです。詳細はそれぞれのデモの中で紹介します。\n準備： 証明書と鍵ペアの作成 暗号化と認証では証明書が、電子署名とその検証では鍵ペアがそれぞれ必要です。後続のデモに備えて、Receptor を起動する前にあらかじめ作成します。\n証明書の作成 必要な証明書には、次のものがあります。\nCA の証明書と秘密鍵 その CA に署名された各ノードのサーバ証明書（またはクライアント証明書）と秘密鍵 Receptor は組み込みで証明書の発行機能を持っているため、これを利用すると簡単に用意できます。いずれもがんばれば Receptor を使わずとも手作りはできますが、特にサーバ証明書（クライアント証明書）は Subject Alternative Name（SAN）に特殊なフィールドを持つ（後述）ため、ちょっと大変です。\nCA 証明書と秘密鍵 は、次のコマンドで作成できます。デフォルトの有効期限は作成時点から 10 年ですが、必要に応じて notbefore と notafter を指定して任意の期限に設定できます（詳細は --help に記載があります）。\nreceptor --cert-init \\ commonname=\u0026#34;\u0026lt;Common Name\u0026gt;\u0026#34; \\ bits=2048 \\ outcert=/path/to/ca.crt \\ outkey=/path/to/ca.key CA 証明書が作成できたら、それを利用して次のコマンドで サーバ証明書兼クライアント証明書 を発行できます。ひとつめのコマンドで証明書署名要求（CSR）を作成し、ふたつめのコマンドでそれに署名しています。これもデフォルトの有効期限は 10 年です。その他のオプションは --help に記載があります。\nreceptor --cert-makereq \\ bits=2048 \\ commonname=\u0026#34;\u0026lt;Common Name\u0026gt;\u0026#34; \\ dnsname=\u0026lt;DNS Name\u0026gt; \\ nodeid=\u0026lt;Node ID\u0026gt; \\ outreq=/path/to/server.csr \\ outkey=/path/to/server.key receptor --cert-signreq \\ req=/path/to/server.csr \\ cacert=/path/to/ca.crt \\ cakey=/path/to/ca.key \\ outcert=/path/to/server.crt 今回のデモでは、全部で 8 ノード分の証明書の発行が必要です。コマンドを逐一組み立てるのも手間ですし、receptor のバイナリを用意するのも手間なので、次のようなコマンドを流して、全部を Docker 内で処理させます（コンテナ内のシェルをとって receptor を叩くのももちろんアリです）。これで、ホスト側の ./certs に生成されたファイル群が保存されます。\nRECEPTOR_IMAGE=\u0026#34;quay.io/ansible/receptor:v1.3.0\u0026#34; NODES=\u0026#34; c01.example.internal:controller01 c02.example.internal:controller02 c03.example.internal:controller03 c04.example.internal:controller04 r01.example.internal:relayer01 e01.example.internal:executor01 e02.example.internal:executor02 e03.example.internal:executor03 \u0026#34; # ルート CA 証明書の作成 docker run --rm --volume \u0026#34;${PWD}/certs:/tmp/certs\u0026#34; ${RECEPTOR_IMAGE} \\ receptor --cert-init commonname=\u0026#34;Receptor Example CA\u0026#34; bits=2048 outcert=/tmp/certs/ca.crt outkey=/tmp/certs/ca.key # サーバ証明書兼クライアント証明書の発行 for item in ${NODES}; do node=(${item//:/ }) echo \u0026#34;Generating new certificate for ${node[0]} (${node[1]})\u0026#34; docker run --rm --volume \u0026#34;${PWD}/certs:/tmp/certs\u0026#34; ${RECEPTOR_IMAGE} \\ receptor --cert-makereq bits=2048 commonname=\u0026#34;${node[1]} example cert\u0026#34; dnsname=${node[0]} nodeid=${node[1]} outreq=/tmp/certs/${node[0]}.csr outkey=/tmp/certs/${node[0]}.key docker run --rm --volume \u0026#34;${PWD}/certs:/tmp/certs\u0026#34; ${RECEPTOR_IMAGE} \\ receptor --cert-signreq req=/tmp/certs/${node[0]}.csr cacert=/tmp/certs/ca.crt cakey=/tmp/certs/ca.key outcert=/tmp/certs/${node[0]}.crt verify=true docker run --rm --volume \u0026#34;${PWD}/certs:/tmp/certs\u0026#34; ${RECEPTOR_IMAGE} \\ rm /tmp/certs/${node[0]}.csr done 生成された証明書の中身は後述のデモで紹介します。\n鍵ペアの作成 鍵ペアは、電子署名とその検証で必要です。これは一般的なお作法に従って openssl で作成できます。\n次のコマンドで、./certs 下に鍵ペアが生成されます（証明書ではないので本当は ./certs 下に配置するのは不適切ですが、一か所にまとめたいのでそうしています）。\nopenssl genrsa -out certs/signworkprivate.pem 2048 openssl rsa -in certs/signworkprivate.pem -pubout -out certs/signworkpublic.pem 電子署名のデモに備えて、上記を別のファイル名でもう一度実行し、鍵ペアを合計で 2 つ作っておきます。\nopenssl genrsa -out certs/signworkprivate.c02.pem 2048 openssl rsa -in certs/signworkprivate.c02.pem -pubout -out certs/signworkpublic.c02.pem ファイルの確認 ./certs 下に次のファイル群が揃っていれば、準備は完了です。\n$ ls -l certs/ total 80 -rw-------. 1 root root 1212 Dec 9 15:58 c01.example.internal.crt -rw-------. 1 root root 1675 Dec 9 15:58 c01.example.internal.key -rw-------. 1 root root 1212 Dec 9 15:58 c02.example.internal.crt -rw-------. 1 root root 1675 Dec 9 15:58 c02.example.internal.key -rw-------. 1 root root 1212 Dec 9 15:58 c03.example.internal.crt -rw-------. 1 root root 1679 Dec 9 15:58 c03.example.internal.key -rw-------. 1 root root 1212 Dec 9 15:58 c04.example.internal.crt -rw-------. 1 root root 1679 Dec 9 15:58 c04.example.internal.key -rw-------. 1 root root 1139 Dec 9 15:58 ca.crt -rw-------. 1 root root 1679 Dec 9 15:58 ca.key -rw-------. 1 root root 1208 Dec 9 15:58 e01.example.internal.crt -rw-------. 1 root root 1679 Dec 9 15:58 e01.example.internal.key -rw-------. 1 root root 1208 Dec 9 15:58 e02.example.internal.crt -rw-------. 1 root root 1679 Dec 9 15:58 e02.example.internal.key -rw-------. 1 root root 1208 Dec 9 15:58 e03.example.internal.crt -rw-------. 1 root root 1679 Dec 9 15:58 e03.example.internal.key -rw-------. 1 root root 1204 Dec 9 15:58 r01.example.internal.crt -rw-------. 1 root root 1679 Dec 9 15:58 r01.example.internal.key -rw-------. 1 kuro kuro 1675 Dec 9 16:05 signworkprivate.c02.pem -rw-------. 1 kuro kuro 1675 Dec 9 16:05 signworkprivate.pem -rw-rw-r--. 1 kuro kuro 451 Dec 9 16:05 signworkpublic.c02.pem -rw-rw-r--. 1 kuro kuro 451 Dec 9 16:05 signworkpublic.pem Docker Compose ファイルの確認 ホスト側の ./certs 下のファイルのうち、そのノードに必要なファイルのみがコンテナ内で /etc/receptor/certs 下にマウントされるよう、Docker Compose ファイルで volumes を構成しています。以下は c01 の例です。\n... c01: ... volumes: - \u0026#34;./certs/ca.crt:/etc/receptor/certs/ca.crt\u0026#34; - \u0026#34;./certs/c01.example.internal.crt:/etc/receptor/certs/c01.example.internal.crt\u0026#34; - \u0026#34;./certs/c01.example.internal.key:/etc/receptor/certs/c01.example.internal.key\u0026#34; - \u0026#34;./certs/signworkprivate.pem:/etc/receptor/certs/signworkprivate.pem\u0026#34; - \u0026#34;./conf/c01.yml:/etc/receptor/receptor.conf\u0026#34; デモ (1)： 接続できるピアの制限 メッシュネットワークを構成する際、バックエンドでの接続方法を指定するため、隣接するノード間で *-listener と *-peer を定義することを 最初のエントリ で紹介しました。\nこのとき、*-listener 側は、他のノードから *-peer で指定されると、デフォルトでは制限なくすべての接続要求を受け入れます。これには、第三者により管理者の意図しないノードをメッシュネットワークに追加されうるリスクがあります。\nこの対策として、*-listener 側で、接続を受け入れるノードをホワイトリスト方式で制限 できます。\n今回のデモでは、r01（relayer01）にこの設定を導入し、c04（controller04）はホワイトリストに含めない 構成で動作を確認します。\n設定ファイルの作成 r01（relayer01）の設定ファイルの tcp-listener で、allowedpeers を設定します。\n... - tcp-listener: port: 7323 ... allowedpeers: - controller01 - controller02 - controller03 - executor01 - executor02 - executor03 これが、バックエンドでの接続を受け入れるノードのホワイトリスト です。記載の通り、controller04 はこのリストには含まれません。\n実際の動き コンテナを起動して、動きを確認します。\ndocker compose up -d c04 のログを見ると、バックエンドの接続に失敗し、経路情報の交換ができていないことがわかります。\n$ docker compose logs c04 c04 | DEBUG 2022/12/09 06:18:49 Running TCP peer connection r01.example.internal:7323 c04 | DEBUG 2022/12/09 06:18:49 Sending initial connection message ... c04 | WARNING 2022/12/09 06:18:54 Backend connection exited (will retry) ... c04 | DEBUG 2022/12/09 06:18:54 Re-calculating routing table c04 | INFO 2022/12/09 06:18:54 Known Connections: c04 | INFO 2022/12/09 06:18:54 controller04: c04 | INFO 2022/12/09 06:18:54 relayer01: c04 | INFO 2022/12/09 06:18:54 Routing Table: ... ホワイトリストを定義した r01 側では、controller04 からの接続を拒否したログが確認できます。\n$ docker compose logs r01 ... r01 | DEBUG 2022/12/09 06:18:49 Listening on TCP [::]:7323 ... r01 | ERROR 2022/12/09 06:18:49 Backend receiving error read tcp 172.21.0.4:7323-\u0026gt;172.21.0.5:54814: use of closed network connection r01 | ERROR 2022/12/09 06:18:49 Backend error: rejected connection with node controller04 because it is not in the allowed peers list ... 正常な他の Controller（例えば c01）でメッシュネットワークの状態を確認すると、controller04 がそもそも参加できていないことが確認できます。\n[root@c01 /]# receptorctl status ... Known Node Known Connections controller01 relayer01: 1 controller02 relayer01: 1 controller03 relayer01: 1 executor01 relayer01: 1 executor02 relayer01: 1 executor03 relayer01: 1 relayer01 controller01: 1 controller02: 1 controller03: 1 executor01: 1 executor02: 1 executor03: 1 ... c04 は、自分自身しか認識できていません。\n[root@c04 /]# receptorctl status ... Known Node Known Connections controller04 relayer01 Node Service Type Last Seen Tags controller04 control Stream 2022-12-09 06:20:57 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} allowedpeers の設定により、バックエンドで接続を許容するノードを制限できることが確認できました。\nデモ (2)： TLS による暗号化と相互認証 これまでのエントリで紹介してきた構成では、明示的な暗号化を行っていませんでした。ここでは、Receptor が持つ TLS 関連の機能、すなわち、サーバ証明書とクライアント証明書による 相互認証と暗号化 の動きを確認します。\n前提： 二種類のネットワークと TLS Receptor が扱うネットワークには、次の二種類があります。\nバックエンドネットワーク（アンダレイネットワーク、Below-the-mesh） メッシュの 下 のネットワーク 隣接するノード間 の通信に利用する 設定では *-listener と *-peer が該当する オーバレイネットワーク（Above-the-mesh） メッシュの 上 のネットワーク メッシュネットワーク上で 任意のノード間 が通信するために利用する 設定では control-service が該当する 少しややこしいですが、Receptor の TLS の設定はこの 二種類のそれぞれで設定がある ため、区別して考える必要があります。\n前提： バックエンドネットワークの TLS 設定 バックエンドネットワーク の TLS 設定は、隣接ノード間の通信 に関連します。すなわち、バックエンドで接続するとき の *-listener をサーバ側、*-peer をクライアント側 として構成し、隣接ノード間の通信を保護します。\nメッシュネットワーク上で発生する通信は、結局は下位のレイヤでバックエンドネットワークを介して対向のノードに届きます。したがって、バックエンドネットワークの TLS さえ設定 すれば、下位ネットワーク上の盗聴やなりすましからは、メッシュネットワーク上の通信も保全される といえます。\nTLS を構成しない状態では、バックエンドの通信は暗号化されません。交換される経路の情報やオーバレイネットワーク上の通信の一部は、パケットを覗けば以下の通り平文で確認できます。\n前提： オーバレイネットワークの TLS 設定 オーバレイネットワーク の TLS 設定は、主に ワークを実行するときの通信 に関連します。具体例は、ワークを実行した時に発生する、Controller ノードと Executor ノードの間での実行指示やパラメータ、ペイロード、実行結果などのやりとりです。オーバレイネットワークの TLS 設定では、Executor の Control Service をサーバ側、Controller をクライアント側 として構成します。\nクライアントのイメージが少しつきにくいですが、雑に言えば、receptorctl で work submit を実行する Controller のことです。\nこうしたオーバレイネットワーク上のノード間のやりとりは、送信元と送信先のノード ID やサービス名を示す ヘッダ と、実際にノード間でやりとりしたい データ本体（例えばワークに伴うパラメータや標準入出力の中身）が連結されたバイト列のメッセージとして授受されます。オーバレイネットワークの TLS 設定は、このうち データ本体 の保護に寄与するものです（ヘッダ部分は保護の範囲外です）。\n前述の通り、バックエンドネットワークの TLS さえ設定すれば、下位ネットワーク上の盗聴やなりすましからはメッシュネットワーク上の通信も保全されるといえます。したがって、オーバレイネットワークの TLS 設定は、下位ネットワーク上よりはむしろ 経路のノード上での盗聴やなりすまし への対策と言えそうです。下位ネットワークの TLS 設定が危殆化したときの二重の備えとも位置付けられます。\nなお、オーバレイネットワークには現在の実装では QUIC が利用されており、QUIC が TLS の設定を必須としていることから、TLS の設定を明示しない場合 でも 起動時に自己署名証明書が発行されて暗号化に利用 されます。このため、明示的に TLS の設定をしなくても、データ本体が平文で見えてしまうことは以下の通りありません。\nしたがって、最低限の暗号化だけを実施したい場合は、明示的な TLS の設定は必須ではありません。ただし、証明書の検証はスキップ されるため、ワークの 送信元と送信先の身元の正当性を保証したい 場合は、やはり 明示的な設定が必要 です。\n前提： 証明書の中身 前述の手順で作成した証明書のうち、CA 証明書 は特に変わったところはありません。自前の証明書への置き換えも容易です。\n$ sudo openssl x509 -text -in certs/ca.crt -noout Certificate: Data: ... Issuer: CN = Receptor Example CA Validity Not Before: Dec 9 05:58:46 2022 GMT Not After : Dec 9 05:58:46 2032 GMT Subject: CN = Receptor Example CA ... 一方で、サーバ証明書（兼クライアント証明書） は、バックエンドネットワークとオーバレイネットワークの 両方 で利用されます。つまり、バックエンド用に ホストの正当性 を保証するだけでなく、オーバレイ用に ノードの正当性 も保証できる必要があります。\nこの目的で、前述の手順で Receptor により発行したサーバ証明書兼クライアント証明書には、Subject Alternative Name（SAN）に、バックエンド用の DNS のホスト名 だけでなく、オーバレイ用の ノード ID も含まれています。たとえば、c01（controller01）の証明書を確認します。\n$ CERT=\u0026#34;certs/c01.example.internal.crt\u0026#34; $ sudo openssl x509 -text -in ${CERT} -noout Certificate: Data: ... Issuer: CN = Receptor Example CA Validity Not Before: Dec 9 05:58:47 2022 GMT Not After : Dec 9 05:58:47 2023 GMT Subject: CN = controller01 example cert ... X509v3 extensions: X509v3 Subject Alternative Name: DNS:c01.example.internal, othername:\u0026lt;unsupported\u0026gt; SAN の DNS にホスト名が含まれるほか、othername の存在 も確認できます。このフィールドをパースすると、Receptor のノード ID 用に予約された OID 1.3.6.1.4.1.2312.19.1 と、実際に設定された ノード ID が見られます。\n$ OFFSET=$(sudo openssl asn1parse -in ${CERT} | grep -A 1 \u0026#34;X509v3 Subject Alternative Name\u0026#34; | tail -n 1 | sed \u0026#39;s/:.*//g\u0026#39;) $ sudo openssl asn1parse -in ${CERT} -strparse ${OFFSET} ... 26:d=2 hl=2 l= 9 prim: OBJECT :1.3.6.1.4.1.2312.19.1 ... 39:d=3 hl=2 l= 12 prim: UTF8STRING :controller01 これにより、ホストとノードの両方の正当性 を保証し、同時に そのホストがたしかにそのノードであることも保証 できるようになっています。\nデモ (2-1)： バックエンドネットワークの TLS まずはバックエンドネットワークの TLS 設定と動作を確認します。\n設定ファイルの作成 前述の通り、*-listener をサーバ側、*-peer をクライアント側 として構成します。今回は tcp-listener と tcp-peer です。\n今回の構成では、tcp-listener は r01（relayer01）で定義されています。設定ファイルを抜粋します。\n... - tls-server: name: example-tls-server cert: /etc/receptor/certs/r01.example.internal.crt key: /etc/receptor/certs/r01.example.internal.key requireclientcert: true clientcas: /etc/receptor/certs/ca.crt - tcp-listener: port: 7323 tls: example-tls-server ... tls-server で TLS のサーバ側の設定を行い、それを tcp-listener で呼び出す形で記述します。\ntls-server には、利用するサーバ証明書と秘密鍵（cert と key）を指定します。ここでは、相手の正当性を双方向で検証させるため、クライアント証明書の要求を有効化（requireclientcert を true に）し、それに利用する CA の証明書を指定（clientcas）しています。一連の設定には name で名前を付けます。\ntcp-listener の tls で tls-server の name を指定することで、この tcp-listener の待ち受けに TLS が利用されるようになります。\n続けて、tcp-peer の定義の例として c01（controller01）の設定ファイルを抜粋します。サーバ側の設定と構造は似ていて、tls-client を定義して tcp-peer で呼び出す形です。\n... - tls-client: name: example-tls-client rootcas: /etc/receptor/certs/ca.crt cert: /etc/receptor/certs/c01.example.internal.crt key: /etc/receptor/certs/c01.example.internal.key - tcp-peer: address: r01.example.internal:7323 tls: example-tls-client ... tls-client では、ここではサーバ証明書の検証に利用する CA の証明書を指定（rootcas）しています。また、サーバ側でクライアント証明書の検証を必須にしたため、クライアント証明書と秘密鍵（cert と key）も指定しています。一連の設定には name で名前を付けます。\ntcp-peer の tls で tls-client の name を指定することで、この tcp-peer の接続に TLS が利用されるようになります。\n実際の動き コンテナを起動し、正常にメッシュネットワークが構成されていることを確認します。\ndocker compose up -d [root@c01 /]# receptorctl status ... Known Node Known Connections controller01 relayer01: 1 controller02 relayer01: 1 controller03 relayer01: 1 executor01 relayer01: 1 executor02 relayer01: 1 executor03 relayer01: 1 relayer01 controller01: 1 controller02: 1 controller03: 1 executor01: 1 executor02: 1 executor03: 1 Route Via controller02 relayer01 controller03 relayer01 executor01 relayer01 executor02 relayer01 executor03 relayer01 relayer01 relayer01 ... バックエンド接続が正常に行われ、経路の交換ができているようです。\n具体的な手順は割愛しますが、パケットを見ると、平文の通信がなくなっていることが確認できます。\n何らかの原因でバックエンド接続ができなかった場合は、ログから原因を特定できます。設定例は割愛しますが、追加で例えば以下のようなことを試して動きを確認するのもよいでしょう。\nクライアント側かサーバ側のどちらかで TLS の設定を無効にする サーバ側の証明書内のホスト名と tcp-peer で指定されるホスト名が一致しない状態にする 証明書内のノード名と実際のノード名が一致しない状態にする いずれの場合も、バックエンドの接続に失敗するか、そもそも Receptor が起動できないかで、正常に動作しません。\nデモ (2-2)： オーバレイネットワークの TLS 続けて、オーバレイネットワークの TLS 設定と動作を確認します。\n設定ファイルの作成 前述の通り、オーバレイネットワークの TLS 設定では、Executor の Control Service をサーバ側、Controller をクライアント側 として構成します。\n今回は、サーバ側 の構成例として e03（executor03）を利用します。設定ファイルを抜粋します。\n... - tls-server: name: example-tls-server cert: /etc/receptor/certs/e03.example.internal.crt key: /etc/receptor/certs/e03.example.internal.key requireclientcert: true clientcas: /etc/receptor/certs/ca.crt - control-service: service: control tls: example-tls-server ... tls-server の記述内容は、前述のバックエンドネットワークの TLS 設定のそれとまったく同じです。オーバレイネットワークでこれを利用するには、それを control-service で呼び出し ます。\nまた、動作を確認するため、e01（executor01）と e03（executor03）で、以下のコマンドワークを定義しています。\n... - work-command: worktype: echo-reply command: bash params: \u0026#34;-c \u0026#39;while read -r line; do echo Reply from $(cat /etc/hostname): ${line^^}; done\u0026#39;\u0026#34; ... クライアント側 の Controller に必要な設定は、tls-client だけです。例として c01（controller01）の設定ファイルを抜粋しますが、前述のバックエンドネットワーク用の tls-client とまったく同じで、そのまま再利用できます。\n... - tls-client: name: example-tls-client rootcas: /etc/receptor/certs/ca.crt cert: /etc/receptor/certs/c01.example.internal.crt key: /etc/receptor/certs/c01.example.internal.key ... ここで定義した name を、receptorctl の work submit の実行時に呼び出す ことになります。\n実際の動き コンテナを起動して、動きを確認します。\ndocker compose up -d メッシュネットワークの状態を見ると、executor03 の Control Service のタイプが StreamTLS であることが確認できます。これが、オーバレイネットワークの Control Service で明示的に TLS の利用を構成した状態です。\n[root@c01 /]# receptorctl status ... Node Service Type Last Seen Tags executor03 control StreamTLS 2022-12-09 06:28:04 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} controller01 control Stream 2022-12-09 06:28:07 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} controller02 control Stream 2022-12-09 06:27:09 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} executor01 control Stream 2022-12-09 06:27:10 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} controller03 control Stream 2022-12-09 06:27:10 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} executor02 control Stream 2022-12-09 06:27:10 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} まずは、TLS を 構成していない Executor に対してワークを送信します。例として executor01 に宛てます。\n[root@c01 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --payload - \\ echo-reply Reply from e01.example.internal: HELLO RECEPTOR! (gCobCpTe, released) コマンドは 以前のエントリ で紹介したものとまったく一緒です。正常に動作しています。\n続けて、TLS を 構成した Executor（executor03）に宛てて同じコマンドを実行します。\n[root@c01 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor03 \\ --rm \\ --follow \\ --payload - \\ echo-reply ERROR: Remote unit failed: TLS error connecting to remote service: CRYPTO_ERROR (0x12a): insecure connection to secure service (fi4rBON8, released) executor03 側の Control Service で TLS の設定が行われているため、このままでは接続を確立できず、ワークが送信できません。\nここで、オプション --tls-client を追加し、この Controller の設定ファイルで記述した tls-client の name を指定します。\n[root@c01 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor03 \\ --rm \\ --follow \\ --payload - \\ --tls-client example-tls-client \\ echo-reply Reply from e03.example.internal: HELLO RECEPTOR! (Pc9E1HSG, released) 正常に実行できるようになりました。これが、ワークの送信を行う Controller と対向の Control Service の間で TLS による相互認証と暗号化が行われた状態です。\nデモ (3)： ファイアウォール 続いて、組み込みのファイアウォール機能の構成と動作を確認します。\n前提： ファイアウォールの考え方 Receptor に組み込まれたファイアウォールは、バックエンドネットワーク向けではなく オーバレイネットワーク向けの機能 です。メッシュネットワーク上の 任意のノードで有効化 でき、任意のノード間の通信を許可または拒否 できます。\n本デモでは、Controller から Executor に通信するときに必ず経由する r01（relayer01）でファイアウォールを有効化し、特定の Controller のみ Executor との通信が許可された状態（ホワイトリスト）を構成します。また、併せて e02（executor02）で、特定の Controller からの接続を拒否する状態（ブラックリスト）を構成します。\n前提： ルールの考え方 ひとつのファイアウォールルールは、次の 4 つの要素と、合致した場合の処理（action）を任意で組み合わせて定義します。詳細は ドキュメント に記載があります。\n送信元ノード ID（fromnode） 送信先ノード ID（tonode） 送信元サービス名（fromservice） 送信先サービス名（toservice） 合致した場合の処理は、許可（accept）、破棄（drop）、拒否（reject）の三種です。\n前提： ルールの評価 ルールは 記述した順に評価 され、最初に合致した条件で処理 されます。また、いずれのルールにも合致しない場合は許可 されます。\nしたがって、つまり ブラックリスト 的な考え方であり、いわゆる 暗黙の Deny はありません。ホワイトリスト 的な使い方をしたい場合は 最後にすべて拒否するルールを明示的に追加 する必要があります。\nまた、いわゆる ステートレス っぽい考え方なので、特に ホワイトリスト 的に使う場合は行きだけでなく 戻りも明示的に許可 が必要です。\n例えば、あるワークを Controller から Executor に送信して実行させるとき、行き の通信は次のような状態です。戻り はそれぞれ逆になります。\n項目 値 送信元ノード ID Controller のノード ID 送信先ノード ID Executor のノード ID 送信元サービス名 自動生成されるランダム文字列 （実行時に起動される一時的なサービスの名称） 送信先サービス名 Executor の Control Service の名称（通常は control） 設定ファイルの作成 今回は、r01（relayer01）で、ホワイトリスト 的な考え方で次の要件を実装します。\ncontroller01 と controller02 から、executor01、02、03 への通信を許可（行き用） executor01、02、03 から、controller01 と controller02 への通信を許可（戻り用） それ以外は拒否 設定ファイルを抜粋します。\n--- - node: id: relayer01 firewallrules: - action: accept fromnode: /controller0[12]/ tonode: /executor0[1-3]/ - action: accept fromnode: /executor0[1-3]/ tonode: /controller0[12]/ - action: accept toservice: unreach - action: reject ... 行頭と末尾を / で囲むことで正規表現で記述できる（ただし完全一致が必要です）ため、ある程度まとめて全部で 4 つのルールを記述しています。\n最初の 2 つのルールが、目的の Controller と Executor 間の通信を明示的に許可するものです。最初が行き、次が戻りです。\nその次の 3 番目のルールでは、サービス名 unreach を宛先とするデータをすべて許可しています。これは、内部的に何らかの原因でメッセージが配送できなかった際に生成される 到達不可メッセージ の配送を、送信元や送信先のノードに関わらずすべて許可するためです。ファイアウォールによって拒否された旨を示すメッセージもこれに該当するため、許可しておくと動きがわかりやすくなります。\n最後の 4 番目のルールで、これまでの 3 つのルールのいずれにも合致しない通信がすべて拒否されます。\n3 番目のルール以外は、サービス名（fromservice や toservice）を指定していません。したがって、ワークのやりとりで発生する通信（サービス名 control との通信）だけでなく receptorctl ping で発生する Ping（サービス名 ping との通信）にも適用されます。\nまた、e02（executor02）では、ブラックリスト 的な考え方で次の要件を実装します。\ncontroller02 からの通信は拒否 片方向だけ閉じれば通信は成立しなくなるため、戻り分は考慮しません。設定ファイルは次の通りです。\n--- - node: id: executor02 firewallrules: - action: reject fromnode: controller02 ... ここでは正規表現ではなくベタ書きしています。\n加えて、executor01、02、03 のそれぞれで、動作確認用の次のコマンドワークを定義しています。\n... - work-command: worktype: echo-reply command: bash params: \u0026#34;-c \u0026#39;while read -r line; do echo Reply from $(cat /etc/hostname): ${line^^}; done\u0026#39;\u0026#34; ... 実際の動き コンテナを起動して、動きを確認します。\ndocker compose up -d 初めに、relayer01 のファイアウォールの動作を確認するため、controller01、02、03 のそれぞれから、executor01 に対してワークを送信します。\n[root@c01 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --payload - \\ echo-reply Reply from e01.example.internal: HELLO RECEPTOR! (0eyPn5a8, released) [root@c02 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --payload - \\ echo-reply Reply from e01.example.internal: HELLO RECEPTOR! (p2qLHTyg, released) [root@c03 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --payload - \\ echo-reply ^C (8B0hLBzU, released) 経路の relayer01 では、Executor との通信は controller01 と 02 のみを許可していました。これにより、controller03 からのワークの送信はできなくなっています（Ctrl+C で中断できます）。--rm と --follow を外してワークを送信してから work list を見ると、Pending で止まっている様子が確認できます。確認後はリリースしておきます。\n[root@c03 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor01 \\ --payload - \\ echo-reply ^C [root@c03 /]# receptorctl work list { \u0026#34;gtAwdBBS\u0026#34;: { \u0026#34;Detail\u0026#34;: \u0026#34;Starting Worker\u0026#34;, ... \u0026#34;State\u0026#34;: 0, \u0026#34;StateName\u0026#34;: \u0026#34;Pending\u0026#34;, ... } } [root@c03 /]# receptorctl work release --all Released: (gtAwdBBS, released) ワークだけでなく、Ping もファイアウォールで拒否されます。\n[root@c01 /]# receptorctl ping executor01 Reply from executor01 in 455.761µs Reply from executor01 in 609.2µs Reply from executor01 in 414.827µs Reply from executor01 in 751.812µs [root@c02 /]# receptorctl ping executor01 Reply from executor01 in 406.311µs Reply from executor01 in 640.188µs Reply from executor01 in 590.659µs Reply from executor01 in 1.32654ms [root@c03 /]# receptorctl ping executor01 ERROR: blocked by firewall ERROR: blocked by firewall ERROR: blocked by firewall ERROR: blocked by firewall c02 のログでは、relayer01 から到達不可メッセージが返ってきたことが確認できます。\n$ docker compose logs c03 ... c03 | WARNING 2022/12/09 06:32:11 Received unreachable message from relayer01 c03 | WARNING 2022/12/09 06:32:12 Received unreachable message from relayer01 c03 | WARNING 2022/12/09 06:32:13 Received unreachable message from relayer01 c03 | WARNING 2022/12/09 06:32:14 Received unreachable message from relayer01 ... 続けて、executor02 のファイアウォールの動作を確認するため、controller01 と 02 から、executor01 と 02 に対してワークを送信します。\n[root@c01 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --payload - \\ echo-reply Reply from e01.example.internal: HELLO RECEPTOR! (cXaxadUZ, released) [root@c01 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor02 \\ --rm \\ --follow \\ --payload - \\ echo-reply Reply from e02.example.internal: HELLO RECEPTOR! (kdnrbl9s, released) [root@c02 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --payload - \\ echo-reply Reply from e01.example.internal: HELLO RECEPTOR! (R0kTKm5W, released) [root@c02 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor02 \\ --rm \\ --follow \\ --payload - \\ echo-reply ^C executor02 では、controller02 からの通信を拒否していました。これにより、controller02 は、executor01 とは通信できる一方で、executor02 には到達できません（Ctrl+C で中断できます）。\nPing も同様です。このパタンでは、到達できなかった旨が即座に返ってきます。\n[root@c01 /]# receptorctl ping executor01 Reply from executor01 in 455.761µs Reply from executor01 in 609.2µs Reply from executor01 in 414.827µs Reply from executor01 in 751.812µs [root@c01 /]# receptorctl ping executor02 Reply from executor02 in 485.397µs Reply from executor02 in 655.979µs Reply from executor02 in 617.134µs Reply from executor02 in 1.550298ms [root@c02 /]# receptorctl ping executor01 Reply from executor01 in 406.311µs Reply from executor01 in 640.188µs Reply from executor01 in 590.659µs Reply from executor01 in 1.32654ms [root@c02 /]# receptorctl ping executor02 ERROR: blocked by firewall ERROR: blocked by firewall ERROR: blocked by firewall ERROR: blocked by firewall c02 のログでは、拒否されたログが確認できます。\n$ docker compose logs c02 ... c02 | WARNING 2022/12/09 06:38:56 Received unreachable message from executor02 c02 | WARNING 2022/12/09 06:38:57 Received unreachable message from executor02 c02 | WARNING 2022/12/09 06:38:58 Received unreachable message from executor02 c02 | WARNING 2022/12/09 06:38:59 Received unreachable message from executor02 ... ここまでで、ファイアウォールの動作が確認できました。\n不要なワークが残っていたらリリースします（ファイアウォールに阻害されるパタンでは、work submit 後の Ctrl+C でワークがリリースされるときとされないときがありそうです）。\n[root@c02 /]# receptorctl work release --all Released: (xFD0u0uh, released) デモ (4)： ワークの電子署名 Receptor は、Executor に届いたワークが正当な Controller から送信されたものであることを検証する手段として、ワークへの署名 とその 署名の検証 を行う機能があります。あらかじめ作成した鍵ペアを利用して、Controller 側が秘密鍵で署名 し、Executor 側が公開鍵で検証 する形です。\n本デモでは、この機能の構成と動作を確認します。\n設定ファイルの作成 Controller 側の設定 前述の通り、署名は Controller 側が行います。例として c01（controller01）の設定ファイルを抜粋します。\n... - work-signing: privatekey: /etc/receptor/certs/signworkprivate.pem tokenexpiration: 5m work-signing で署名に利用する 秘密鍵 を指定しています。tokenexpiration は署名の有効期間で、今回は 5 分としています。有効期間を超過すると、Executor 側でワークの検証が失敗します。短くする場合は Controller と Executor の間の時刻のズレに注意が必要です。\nController 側で必要な設定はこれだけです。\n今回は、署名の検証が正しく機能していることを確認するため、c02（controller02）には 別の秘密鍵 を使って署名する設定を追加しています。\n... - work-signing: privatekey: /etc/receptor/certs/signworkprivate.c02.pem tokenexpiration: 5m Executor 側の設定 Executor は、署名を検証する側です。署名に利用された秘密鍵とペアになっている 公開鍵 を利用します。e01（executor01）の設定ファイルを抜粋します。\n... - work-verification: publickey: /etc/receptor/certs/signworkpublic.pem - work-command: worktype: echo-verified-reply command: bash params: \u0026#34;-c \u0026#39;while read -r line; do echo Reply from $(cat /etc/hostname) for verified work: ${line^^}; done\u0026#39;\u0026#34; verifysignature: true work-verification で、検証に利用する公開鍵を指定します。\nまた、実行前に署名を検証させたいワーク（今回は echo-verified-reply）で、署名の検証（verifysignature）を有効（true）にします。今回は work-command でこれを有効化していますが、前回のエントリ で紹介した work-kubernetes でも同様に指定できます。\nまったく同じ設定を、e03（executor03）にも追加しています。\n実際の動き コンテナを起動して、動きを確認します。\ndocker compose up -d 署名の検証が有効化されたワークの定義（ワークタイプ）は、通常のそれとは区別されて表示されます。\n[root@c01 /]# receptorctl status ... Node Work Types executor02 echo-reply executor01 echo-reply executor03 echo-reply Node Secure Work Types executor01 echo-verified-reply executor03 echo-verified-reply executor01 と 03 の echo-verified-reply が、Secure Work Types として表示されており、署名の検証が有効化されていることがわかります。\nc01（controller01）から、このワークタイプを実行します。\n[root@c01 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --payload - \\ echo-verified-reply ERROR: Remote error: ERROR: could not parse response: ERROR: could not verify signature: signature is empty これまで通りのコマンドでは、ワークへの署名は行われません。このため、Executor 側で署名を検証できずエラーになっています。\nワークに署名をするには、work submit に --signwork を追加します。\n[root@c01 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --payload - \\ --signwork \\ echo-verified-reply Reply from e01.example.internal for verified work: HELLO RECEPTOR! (PvvdSwrU, released) 正常に実行できました。これが、署名が正しく検証された状態です。\n続けて、c02（controller02）からも同じワークタイプを署名付きで実行します。\n[root@c02 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --payload - \\ --signwork \\ echo-verified-reply ERROR: Remote error: ERROR: could not parse response: ERROR: could not verify signature: crypto/rsa: verification error controller02 では、設定ファイルで意図的に 別の鍵ペアの秘密鍵 を署名に使うように構成していました。このため、Executor 側ではワークの署名が不正なものとして扱われ、実行が拒否されています。\n署名とその検証により、正しい鍵を持っていなければ Executor にワークが送信できなくなることが確認できました。\nなお、executor03 では、オーバレイネットワークの TLS 設定と署名検証の両方を有効にしていました。このような構成の Executor にワークを送信するには、work submit 時に --tls-client と --signwork の両方を追加する必要があります。\n[root@c01 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor03 \\ --rm \\ --follow \\ --payload - \\ echo-verified-reply ERROR: Remote unit failed: TLS error connecting to remote service: CRYPTO_ERROR (0x12a): insecure connection to secure service (GWtzT1HE, released) [root@c01 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor03 \\ --rm \\ --follow \\ --payload - \\ --tls-client example-tls-client \\ echo-verified-reply ERROR: Remote error: ERROR: could not parse response: ERROR: could not verify signature: signature is empty [root@c01 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor03 \\ --rm \\ --follow \\ --payload - \\ --tls-client example-tls-client \\ --signwork \\ echo-verified-reply Reply from e03.example.internal for verified work: HELLO RECEPTOR! (PA6EyrXs, released) まとめ Receptor のセキュリティ関連の機能をいくつか紹介しました。相応に厳重なつくりになっているようですね。\nReceptor 関連エントリ Receptor (1)： Receptor はじめの一歩 Receptor (2)： Kubernetes 上でのワークの実行 Receptor (3)： 暗号化と認証、ファイアウォール、電子署名 Receptor (4)： AWX と Automation Mesh での Receptor の使われ方 Receptor (5)： AWX で Hop Node 込みの Automation Mesh を強引に構成する ","date":"2022-12-11T22:49:10Z","image":"/archives/4791/img/image-342.png","permalink":"/archives/4791/","title":"Receptor (3)： 暗号化と認証、ファイアウォール、電子署名"},{"content":"はじめに 前回のエントリ では、Receptor によるメッシュネットワークの構成と、メッシュネットワークを介したリモートノードでのコマンド実行、障害からの回復性を紹介しました。\n本エントリでは、リモートノードで何らかの処理をさせるもう一つの手段である、Kubernetes クラスタ上でのワークの実行（Kubernetes ワーク）を紹介します。\nこの機能を利用すると、Receptor の Executor ノード から 任意の Kubernetes クラスタに任意の構成の Pod を作成 できます。Pod で利用するイメージ、そこで実行する コマンドと引数 だけでなく、Pod 自体もカスタマイズでき ます。また、前回のエントリ で紹介したリモートノードでのコマンド実行と同様、任意のペイロードをコマンドの標準入力に渡す ことも引き続き可能なため、非常に柔軟な処理を行えます。\n構成例 今回は、図の 4 ノードで構成します。node01 から node03 を前回同様 Docker 上のコンテナとして起動し、node04 は Kubernetes 上の Pod として起動します。\nController は前回同様 controller01（node01）、Executor は controller01（node01）と executor01（node03）、executor02（node04）の 3 ノードです。\n必要なファイル群は、前回同様 GitHub のリポジトリに配置 しています。\n前提： 基本的な考え方 前回のエントリ で紹介したリモートノードでのコマンド実行（コマンドワーク）では、Executor は指定されたコマンドをそのノード自身で直接実行しました。下図は cat /etc/hostname をリモートノードで実行させた場合の動作イメージです。\n一方で、今回扱う Kubernetes クラスタ上でのワーク（Kubernetes ワーク）では、Executor の役割は Kuberentes 上への Worker と呼ばれる Pod の作成 です。ワークに指定したコマンドや引数は、この Worker Pod の起動コマンド（エントリポイント）として渡されます。したがって、実際のコマンドは Executor 自身ではなくさらにその先の Worker Pod で実行されることになります。下図は cat /etc/hostname を Kubernetes ワークとして実行させた場合の動作イメージです。\nこうした Pod の作成や、Pod の標準入力へのペイロードの連携、標準出力の取得は、技術的にはすべて Kubernetes の API 経由で行われます。\n前提： Kubernetes クラスタへの認証 Kubernetes クラスタ上に Pod を作成するには、当然ながら認証（と認可）が必要です。Receptor は、次の 2 種類の認証をサポートしています。\nkubeconfig 任意の kubeconfig ファイルを利用して認証する方式 kubeconfig ファイルさえあれば Executor ノードの構成に関わらず任意の Kubernetes クラスタ上に Pod を作成できる incluster Executor ノード自体が Kubernetes クラスタ上の Pod として起動している場合に利用できる方式 Executor の Pod を動作させている Service Account に認可された範囲で Pod を作成できる デフォルトは incluster ですが、本エントリでは両方のパタンを実際に試します。今回の構成で executor02（node04）を Kubernetes クラスタ上の Pod として配置しているのは、incluster を試すためです。\nデモ (1)： 基本の動きの確認 まずは単純な例として、前回のエントリ でも利用した、5 秒の sleep を 12 回繰り返すだけの簡単なコマンドを Kubernetes ワークとして動作させます。\n設定ファイルの作成 必要なファイルを準備します。実ファイル群は GitHub のリポジトリに配置 しています。\nReceptor の Executor 用の設定ファイル Kubernetes ワークを実行させる Executor として、Docker 上の executor01（node03）を利用します。設定ファイルを抜粋します。\n... - work-kubernetes: worktype: echo-sleep authmethod: kubeconfig kubeconfig: /tmp/.kube/config namespace: receptor image: quay.io/centos/centos:stream8 command: bash params: \u0026#34;-c \u0026#39;for i in $(seq 1 12); do echo ${i}: $(date +%T); sleep 5; done\u0026#39;\u0026#34; ... work-kubernetes が、Kubernetes ワークを定義する部分で、ここではこのワークにワークタイプ名として echo-sleep と名前を付けています。\nauthmethod は認証方式の指定で、kubeconfig ファイルを利用する kubeconfig とし、パスを次の設定 kubeconfig で指定しています。この指定の仕方をする場合、kubeconfig ファイルは Controller 側ではなく Executor 側（つまりこのノード上）に存在している必要があります（少しややこしいですが、後述する receptorctl に kubeconfig のパスを渡す書き方の場合は Controller 側のパスで記述するため、混同に注意です）。\nnamespace、image、command、params が、作成される Pod の構成を変更する部分です。それぞれ、Pod が作成される Namespace、Pod 内のコンテナで利用するイメージ、そのイメージで起動するコマンドとその引数を指定しています。\nkubeconfig ファイル 利用する kubeconfig ファイルは、今回は Kubernetes クラスタが K3s のため、/etc/rancher/k3s/k3s.yaml をコピーして利用します。既定でエンドポイントに 127.0.0.1:6443 が指定されているため、コンテナ内からも到達できるように Docker ホストの実 IP アドレス（今回は 192.168.0.219）に書き換えます。\ncp /etc/rancher/k3s/k3s.yaml .kube/config sed -i \u0026#39;s/127\\.0\\.0\\.1/192.168.0.219/g\u0026#39; .kube/config Docker Compose ファイル 用意した kubeconfig ファイルは、Docker Compose ファイルで node03 の /tmp/.kube/config にマウントさせています。\n... node03: image: ${RECEPTOR_IMAGE:?err} container_name: node03 hostname: node03.example.internal command: \u0026#34;receptor -c /etc/receptor/receptor.conf\u0026#34; volumes: - \u0026#34;./conf/node03.yml:/etc/receptor/receptor.conf\u0026#34; - \u0026#34;./.kube/config:/tmp/.kube/config\u0026#34; ... 実際の動き Kubernetes クラスタに Namespace receptor を作成して、コンテナを起動させて動きを確認します。\n$ kubectl apply -f manifests/namespace.yml namespace/receptor created $ docker compose up -d $ docker compose exec -it node01 bash [root@node01 /]# ワークを実行するコマンドは、前回のエントリ と同じです。\n[root@node01 /]# receptorctl work submit \\ --node executor01 \\ --no-payload \\ echo-sleep Result: Job Started Unit ID: jBLBH6pV ワークが実行されたら、Kubernetes クラスタ側の様子を確認します。Namespace に receptor を指定していたので、その Namespace の Pod を追いかけます。\n$ kubectl -n receptor get pod NAME READY STATUS RESTARTS AGE echo-sleep-mqnjl 1/1 Running 0 13s $ kubectl -n receptor get pod echo-sleep-mqnjl -o yaml apiVersion: v1 kind: Pod metadata: ... generateName: echo-sleep- name: echo-sleep-mqnjl namespace: receptor ... spec: containers: - args: - -c - \u0026#39;for i in $(seq 1 12); do echo ${i}: $(date +%T); sleep 5; done\u0026#39; command: - bash image: quay.io/centos/centos:stream8 imagePullPolicy: IfNotPresent name: worker ... Pod echo-sleep-mqnjl が Namespace receptor に作成され動作していることがわかります。Pod 名は、ワークの名称（echo-sleep）から生成されるようです。また、Pod の image や command、args も指定した通りに構成されていることがわかります。\n前回のエントリ で紹介した一時ファイルの様子も確認します。\n[root@node01 /]# ls -l /tmp/receptor/controller01/jBLBH6pV/ total 8 -rw-------. 1 root root 317 Dec 4 19:46 status -rw-------. 1 root root 0 Dec 4 19:46 status.lock -rw-------. 1 root root 0 Dec 4 19:45 stdin -rw-------. 1 root root 147 Dec 4 19:46 stdout [root@node01 /]# cat /tmp/receptor/controller01/jBLBH6pV/status { \u0026#34;State\u0026#34;: 2, \u0026#34;Detail\u0026#34;: \u0026#34;Finished\u0026#34;, \u0026#34;StdoutSize\u0026#34;: 147, ... \u0026#34;ExtraData\u0026#34;: { ... \u0026#34;RemoteUnitID\u0026#34;: \u0026#34;zfr7GxnR\u0026#34;, ... } } [root@node03 /]# ls -l /tmp/receptor/executor01/zfr7GxnR/ total 8 -rw-------. 1 root root 3292 Dec 4 19:46 status -rw-------. 1 root root 0 Dec 4 19:46 status.lock -rw-------. 1 root root 0 Dec 4 19:45 stdin -rw-------. 1 root root 147 Dec 4 19:46 stdout [root@node03 /]# cat /tmp/receptor/executor01/zfr7GxnR/status { \u0026#34;State\u0026#34;: 2, \u0026#34;Detail\u0026#34;: \u0026#34;Finished\u0026#34;, \u0026#34;StdoutSize\u0026#34;: 147, ... \u0026#34;ExtraData\u0026#34;: { \u0026#34;Image\u0026#34;: \u0026#34;quay.io/centos/centos:stream8\u0026#34;, \u0026#34;Command\u0026#34;: \u0026#34;bash\u0026#34;, \u0026#34;Params\u0026#34;: \u0026#34;-c \u0026#39;for i in $(seq 1 12); do echo ${i}: $(date +%T); sleep 5; done\u0026#39;\u0026#34;, \u0026#34;KubeNamespace\u0026#34;: \u0026#34;receptor\u0026#34;, \u0026#34;KubeConfig\u0026#34;: \u0026#34;apiVersion: v1\\n...\u0026#34;, \u0026#34;KubePod\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;PodName\u0026#34;: \u0026#34;echo-sleep-mqnjl\u0026#34; } } このあたりの動きも、前回のエントリ で紹介したコマンドワークの時と同じです。status の ExtraData に Kubernetes 固有の情報が含まれるくらいで、大きくは変わらないことがわかります。\n結果もこれまで通り確認できます。\n[root@node01 /]# receptorctl work list --unit_id jBLBH6pV { \u0026#34;jBLBH6pV\u0026#34;: { \u0026#34;Detail\u0026#34;: \u0026#34;Finished\u0026#34;, \u0026#34;ExtraData\u0026#34;: { ... }, \u0026#34;State\u0026#34;: 2, \u0026#34;StateName\u0026#34;: \u0026#34;Succeeded\u0026#34;, \u0026#34;StdoutSize\u0026#34;: 147, ... } } [root@node01 /]# receptorctl work results jBLBH6pV 1: 19:45:18 2: 19:45:23 ... 11: 19:46:08 12: 19:46:13 ここまでで、Kubernetes ワークの基本的な動作が確認できました。Pod の構成と kubeconfig は Kubernetes ならではですが、見かけ上の動きは コマンドワーク と大きくは変わりません。\nワークはリリースしておきます。\n[root@node01 /]# receptorctl work release --all Released: (jBLBH6pV, released) デモ (2)： コマンドへの引数と標準入力の連携 前回のエントリ で紹介したコマンドワークへの引数と標準入力の連携は、Kuberentes ワークでも同じように利用できます。このデモでは、次のことを試します（三つ目はオマケです）。\n実行時に、任意の引数をコマンドへ追加する 実行時に、任意のペイロードを標準入力としてコマンドへ渡す 実行時に、任意のイメージでコマンドを実行する（参考） 設定ファイルの作成 引き続き、Executor として executor01（node03）を利用します。設定ファイルのうち、関連する部分を抜粋します。実ファイル群は GitHub のリポジトリに配置 しています。\n... - work-kubernetes: worktype: cat-files authmethod: kubeconfig kubeconfig: /tmp/.kube/config namespace: receptor image: quay.io/centos/centos:stream8 command: grep params: \u0026#34;-n -H \u0026#39;\u0026#39;\u0026#34; allowruntimeparams: true allowruntimecommand: true - work-kubernetes: worktype: echo-reply authmethod: kubeconfig kubeconfig: /tmp/.kube/config namespace: receptor image: quay.io/centos/centos:stream8 command: bash params: \u0026#34;-c \u0026#39;while read -r line; do echo Reply from $(cat /etc/hostname): ${line^^}; done\u0026#39;\u0026#34; ... 引数で渡されたファイルを grep する cat-files と、標準入力を echo し返す echo-reply を定義しています。\ncat-files では、allowruntimeparams と allowruntimecommand を true にしています。allowruntimeparams が実行時の params の追加を許可する指定です。allowruntimecommand は、デモの本筋とは若干逸れますが、実行時の image の変更を許可する指定です。\n実際の動き コンテナを起動して動きを確認します。\n$ docker compose up -d $ docker compose exec -it node01 bash [root@node01 /]# 実行時の引数の追加 コマンドワークでは、引数の追加は --param params=\u0026lt;追加したい引数\u0026gt; でしたが、Kubernetes ワークに引数を追加する場合は、--param kube_params=\u0026lt;追加したい引数\u0026gt; で指定します。\n[root@node01 /]# receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --no-payload \\ --param \u0026#34;kube_params=/etc/hostname /etc/os-release\u0026#34; \\ cat-files /etc/hostname:1:cat-files-ld5zs /etc/os-release:1:NAME=\u0026#34;CentOS Stream\u0026#34; /etc/os-release:2:VERSION=\u0026#34;8\u0026#34; /etc/os-release:3:ID=\u0026#34;centos\u0026#34; /etc/os-release:4:ID_LIKE=\u0026#34;rhel fedora\u0026#34; /etc/os-release:5:VERSION_ID=\u0026#34;8\u0026#34; /etc/os-release:6:PLATFORM_ID=\u0026#34;platform:el8\u0026#34; /etc/os-release:7:PRETTY_NAME=\u0026#34;CentOS Stream 8\u0026#34; /etc/os-release:8:ANSI_COLOR=\u0026#34;0;31\u0026#34; /etc/os-release:9:CPE_NAME=\u0026#34;cpe:/o:centos:centos:8\u0026#34; /etc/os-release:10:HOME_URL=\u0026#34;https://centos.org/\u0026#34; /etc/os-release:11:BUG_REPORT_URL=\u0026#34;https://bugzilla.redhat.com/\u0026#34; /etc/os-release:12:REDHAT_SUPPORT_PRODUCT=\u0026#34;Red Hat Enterprise Linux 8\u0026#34; /etc/os-release:13:REDHAT_SUPPORT_PRODUCT_VERSION=\u0026#34;CentOS Stream\u0026#34; (y6UCfqYt, released) 結果から指定したファイルが grep できていることがわかります。\n実際の Pod はすぐに消されてしまいますが、急げば定義も確認できます。\n$ kubectl -n receptor get pod NAME READY STATUS RESTARTS AGE cat-files-ld5zs 0/1 Completed 0 1s $ kubectl -n receptor get pod -o yaml ... - apiVersion: v1 kind: Pod metadata: ... generateName: cat-files- name: cat-files-ld5zs namespace: receptor ... spec: containers: - args: - -n - -H - \u0026#34;\u0026#34; - /etc/hostname - /etc/os-release command: - grep image: quay.io/centos/centos:stream8 imagePullPolicy: IfNotPresent name: worker ... Pod の定義から、確かに args に引数が追加されていることが確認できます。\nなお、コマンドワークではワークタイプ名に続けて追加したい引数をベタ書きできました（cat-files -- /etc/hostname /etc/os-release など）が、Kubernetes ワークではこの表記は機能しません。\n実行時の標準入力の利用 標準入力の渡し方は コマンドワーク と全く同じです。--payload でファイルまたは receptorctl への標準入力を、--payload-literal で任意の文字列をそれぞれ渡せます。実行例だけ列挙します。\n[root@node01 /]# echo \u0026#34;Hello Receptor!\u0026#34; \u0026gt; /tmp/input.txt [root@node01 /]# receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --payload /tmp/input.txt \\ echo-reply Reply from echo-reply-64l6z: HELLO RECEPTOR! (mW6CH1D9, released) [root@node01 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --payload - \\ echo-reply Reply from echo-reply-jf4bd: HELLO RECEPTOR! (vmjsnjSd, released) [root@node01 /]# receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --payload-literal \u0026#34;Hello Receptor!\u0026#34; \\ echo-reply Reply from echo-reply-4jzxn: HELLO RECEPTOR! (MUdlZNqN, released) 実行時のイメージの変更（参考） デモの本筋とは若干逸れますが、cat-files では、設定ファイルで allowruntimecommand を true にして、実行時の image の変更を許可していました。これを利用するには、--param kube_image=\u0026lt;イメージ\u0026gt; を追加します。例えば、ubuntu:22.04 を指定して実行します。\n[root@node01 /]# receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --no-payload \\ --param kube_image=ubuntu:22.04 \\ --param \u0026#34;kube_params=/etc/hostname /etc/os-release\u0026#34; \\ cat-files /etc/hostname:1:cat-files-tqwj9 /etc/os-release:1:PRETTY_NAME=\u0026#34;Ubuntu 22.04.1 LTS\u0026#34; /etc/os-release:2:NAME=\u0026#34;Ubuntu\u0026#34; /etc/os-release:3:VERSION_ID=\u0026#34;22.04\u0026#34; /etc/os-release:4:VERSION=\u0026#34;22.04.1 LTS (Jammy Jellyfish)\u0026#34; /etc/os-release:5:VERSION_CODENAME=jammy /etc/os-release:6:ID=ubuntu /etc/os-release:7:ID_LIKE=debian /etc/os-release:8:HOME_URL=\u0026#34;https://www.ubuntu.com/\u0026#34; /etc/os-release:9:SUPPORT_URL=\u0026#34;https://help.ubuntu.com/\u0026#34; /etc/os-release:10:BUG_REPORT_URL=\u0026#34;https://bugs.launchpad.net/ubuntu/\u0026#34; /etc/os-release:11:PRIVACY_POLICY_URL=\u0026#34;https://www.ubuntu.com/legal/terms-and-policies/privacy-policy\u0026#34; /etc/os-release:12:UBUNTU_CODENAME=jammy (jplGpy12, released) Ubuntu らしい /etc/os-release の中身が返ってきました。Pod の定義上も image が変わっていることが確認できます。\n$ kubectl -n receptor get pod NAME READY STATUS RESTARTS AGE cat-files-tqwj9 0/1 Completed 0 1s $ kubectl -n receptor get pod -o yaml ... - apiVersion: v1 kind: Pod metadata: ... generateName: cat-files- name: cat-files-vkgzj namespace: receptor ... spec: containers: - args: - -n - -H - \u0026#34;\u0026#34; - /etc/hostname - /etc/os-release command: - grep image: ubuntu:22.04 imagePullPolicy: IfNotPresent name: worker ... デモ (3)： Pod のカスタマイズ これまでのデモで示したように、設定ファイルの work-kubernetes 中の namespace、image、command、params を活用することで、Executor によって起動される Pod の構成を変更できます。また、allowruntime* を指定すれば、これらは実行時の --param で kube_* により動的にも指定できます（対応表は ドキュメント に掲載されています）。\n一方で、この 4 項目だけでは充分なカスタマイズができるとは言えません。例えば、Pod の CPU やメモリを管理したい場合や、プライベートレジストリの認証情報を渡したい場合、PV をマウントしたい場合、Pod で Init コンテナやサイドカーコンテナを動かしたい場合などには対応できません。\nこうした要件に対応するため、Executor が起動させる Pod のマニフェストを丸ごと指定できる ようになっています。本デモでは、次のことを試します（三つ目はオマケです）。\n事前に指定したマニフェストで Pod を作成させる 実行時に指定したマニフェストで Pod を作成させる 実行時に指定した kubeconfig ファイルで Pod を作成させる（参考） 設定ファイルの作成 必要なファイルを準備します。実ファイル群は GitHub のリポジトリに配置 しています。\nマニフェストの作成 作成される Pod のマニフェストでは、少なくとも worker と名付けられたコンテナが一つ含まれる 必要があります。また、本エントリ公開時点の実装では、複数のコンテナを含めた場合、worker コンテナがあまりにも早く終了しすぎるとワークがエラーになる（Issue）ほか、worker コンテナが完了した段階でワークも完了になる（--rm を付けるとその時点でその他のコンテナの終了処理が始まる）ため、若干の注意が必要です。\nまた、Pod のマニフェストを指定すると、設定ファイルの image や command、params（やそれに対応する実行時の kube_*）の指定が許可されない（または効果がなくなる）ので、マニフェストの段階で完全なものを指定しておく必要があります。これらを実行時に都度指定したい要件がある場合は、マニフェストをまず修正し、実行時にマニフェストを指定するパタン（後述）を使う必要があるでしょう。\n今回は、任意のマニフェストを読み込ませられることが確認できればよいので、ごく簡単な例として次の要件を考えます。\nラベル app: hello-receptor あり コンテナに CPU のリクエスト値あり Init コンテナ（sleep するだけ）あり CentOS Stream 8 のイメージを利用 `grep -n -H ““ /etc/hostname /etc/os-release” を実行 これに従い、以下の内容で manifests/pod.yml を作成します。\napiVersion: v1 kind: Pod metadata: namespace: receptor labels: app: hello-receptor spec: initContainers: - name: init-worker image: quay.io/centos/centos:stream8 command: - sleep args: - \u0026#34;5\u0026#34; containers: - name: worker image: ubuntu:22.04 command: - grep args: - -n - -H - \u0026#34;\u0026#34; - /etc/hostname - /etc/os-release resources: requests: cpu: 10m Receptor の Executor 用の設定ファイル マニフェストを 事前に設定ファイルで指定 するパタンでは、Executor に executor01（node03）を利用します。このパタンでは、マニフェストは Executor 側に配置 する必要があります。設定ファイルの該当箇所は次の通りです。\n... - work-kubernetes: worktype: custom-predefined-pod authmethod: kubeconfig kubeconfig: /tmp/.kube/config pod: /tmp/pod.yml allowruntimecommand: true ... pod が Pod のマニフェストファイルのパスを指定する箇所です。今回は Docker 環境なので、このパスには Docker ホスト側の ./manifests/pod.yml を後述の Docker Compose ファイルでマウントさせます。\nallowruntimecommand は本来は不要ですが、本エントリ公開時点では pod を指定する場合に allowruntimecommand か allowruntimepod（後述）が許可されていないとコケる（Issue / PR）ようなので仕方なく足しています（おそらくバグ）。\nマニフェストを 実行時に receptorctl 経由で指定 するパタンでは、Executor に controller01（node01）を指定します。executor01 を利用しないのは、次の仕様があるからです。\nマニフェストを実行時に指定する場合、マニフェストファイルは Controller 側で読み取られる 読み取った内容は Executor に送信 され利用される マニフェストは Receptor 的には秘匿対象 である 秘匿対象の情報は バックエンドが TLS で暗号化されていないと他のノードに送信できない 今回、TLS をまったく構成していないので、Controller で読み取ったマニフェストは Controller 以外のノードに送れません。逆に言えば他のノードに送らなければ使えるので、今回は Controller 兼 Executor である controller01 に閉じて実行できるように構成します。\nなお、TLS を構成して他のノードの Executor を利用する場合、TLS は最低限 Controller から次のノードまでの間で構成されていればよいようです（盗聴対策としてはエンドツーエンドでなければあまり意味がないですが）。\ncontroller01（node01）の設定ファイルに次の work-kubernetes を追加します。\n... - work-kubernetes: worktype: custom-runtime-pod authmethod: kubeconfig allowruntimepod: true allowruntimeauth: true ... allowruntimepod が、実行時に Pod のマニフェストを指定できるようにするオプションです。allowruntimeauth は本デモの本筋ではないですが、kubeconfig ファイルを実行時に指定する場合のオプションです。\nDocker Compose ファイル node01 には、マニフェストと kubeconfig ファイルを 実行時に指定するため に volumes にエントリを追加します。node03 にも追加しますが、これはマニフェストと kubeconfig ファイルを 設定ファイルで事前に指定したため です。\nservices: node01: ... volumes: - \u0026#34;./conf/node01.yml:/etc/receptor/receptor.conf\u0026#34; - \u0026#34;./.kube/config:/tmp/.kube/config\u0026#34; - \u0026#34;./manifests/pod.yml:/tmp/pod.yml\u0026#34; ... node03: ... volumes: - \u0026#34;./conf/node03.yml:/etc/receptor/receptor.conf\u0026#34; - \u0026#34;./.kube/config:/tmp/.kube/config\u0026#34; - \u0026#34;./manifests/pod.yml:/tmp/pod.yml\u0026#34; 実際の動き コンテナを起動させて、動きを確認します。\n$ docker compose up -d $ docker compose exec -it node01 bash [root@node01 /]# 設定ファイルで指定したマニフェストの利用 executor01 に対してワークを実行して、作成された Pod の定義を確認します。\n[root@node01 /]# receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --no-payload \\ custom-predefined-pod /etc/hostname:1:custom-predefined-pod-rcptp /etc/os-release:1:PRETTY_NAME=\u0026#34;Ubuntu 22.04.1 LTS\u0026#34; /etc/os-release:2:NAME=\u0026#34;Ubuntu\u0026#34; /etc/os-release:3:VERSION_ID=\u0026#34;22.04\u0026#34; /etc/os-release:4:VERSION=\u0026#34;22.04.1 LTS (Jammy Jellyfish)\u0026#34; /etc/os-release:5:VERSION_CODENAME=jammy /etc/os-release:6:ID=ubuntu /etc/os-release:7:ID_LIKE=debian /etc/os-release:8:HOME_URL=\u0026#34;https://www.ubuntu.com/\u0026#34; /etc/os-release:9:SUPPORT_URL=\u0026#34;https://help.ubuntu.com/\u0026#34; /etc/os-release:10:BUG_REPORT_URL=\u0026#34;https://bugs.launchpad.net/ubuntu/\u0026#34; /etc/os-release:11:PRIVACY_POLICY_URL=\u0026#34;https://www.ubuntu.com/legal/terms-and-policies/privacy-policy\u0026#34; /etc/os-release:12:UBUNTU_CODENAME=jammy (MM4c8e9s, released) $ kubectl -n receptor get pod -o yaml ... - apiVersion: v1 kind: Pod metadata: ... labels: app: hello-receptor name: custom-predefined-pod-rcptp namespace: receptor ... spec: containers: - args: - -n - -H - \u0026#34;\u0026#34; - /etc/hostname - /etc/os-release command: - grep image: ubuntu:22.04 imagePullPolicy: IfNotPresent name: worker resources: requests: cpu: 10m ... initContainers: - args: - \u0026#34;5\u0026#34; command: - sleep image: quay.io/centos/centos:stream8 imagePullPolicy: IfNotPresent name: init-worker ... マニフェストファイルで記述した通り、ラベルやリクエスト値、Init コンテナが構成されていることがわかります。\n実行時に指定したマニフェストの利用 実行時にマニフェストを指定するには、receptorctl に --param secret_kube_pod=@\u0026lt;ファイルパス\u0026gt; を指定します。前述の通り、これは Controller 側のファイルパス です。@ を付与することで、その中身が読み取られて利用されます（コマンドが長くなりますが、@ なしでマニフェストを直接文字列として渡すことも可能です、この場合は YAML でなく JSON がよいですね）。\n同様に、--param secret_kube_config=@\u0026lt;ファイルパス\u0026gt; で kubeconfig ファイルも実行時に指定できます。\n[root@node01 /]# receptorctl work submit \\ --node controller01 \\ --rm \\ --follow \\ --no-payload \\ --param secret_kube_config=@/tmp/.kube/config \\ --param secret_kube_pod=@/tmp/pod.yml \\ custom-runtime-pod /etc/hostname:1:custom-runtime-pod-znh8k /etc/os-release:1:PRETTY_NAME=\u0026#34;Ubuntu 22.04.1 LTS\u0026#34; /etc/os-release:2:NAME=\u0026#34;Ubuntu\u0026#34; /etc/os-release:3:VERSION_ID=\u0026#34;22.04\u0026#34; /etc/os-release:4:VERSION=\u0026#34;22.04.1 LTS (Jammy Jellyfish)\u0026#34; /etc/os-release:5:VERSION_CODENAME=jammy /etc/os-release:6:ID=ubuntu /etc/os-release:7:ID_LIKE=debian /etc/os-release:8:HOME_URL=\u0026#34;https://www.ubuntu.com/\u0026#34; /etc/os-release:9:SUPPORT_URL=\u0026#34;https://help.ubuntu.com/\u0026#34; /etc/os-release:10:BUG_REPORT_URL=\u0026#34;https://bugs.launchpad.net/ubuntu/\u0026#34; /etc/os-release:11:PRIVACY_POLICY_URL=\u0026#34;https://www.ubuntu.com/legal/terms-and-policies/privacy-policy\u0026#34; /etc/os-release:12:UBUNTU_CODENAME=jammy (Jk6LcMtY, released) Pod の定義からは、意図通りに構成されていることがわかります。\n$ kubectl -n receptor get pod -o yaml ... - apiVersion: v1 kind: Pod metadata: ... labels: app: hello-receptor name: custom-runtime-pod-98bmc namespace: receptor ... spec: containers: - args: - -n - -H - \u0026#34;\u0026#34; - /etc/hostname - /etc/os-release command: - grep image: ubuntu:22.04 imagePullPolicy: IfNotPresent name: worker resources: requests: cpu: 10m ... initContainers: - args: - \u0026#34;5\u0026#34; command: - sleep image: quay.io/centos/centos:stream8 imagePullPolicy: IfNotPresent name: init-worker ... デモ (4)： In-Cluster Authentication の利用 これまでのデモでは、いずれの Executor も Kubernetes の外で動作しており、Kubernetes への認証には kubeconfig ファイルを利用していました。\n一方で、Executor 自身が Kubernetes 上の Pod として動作している場合、その Pod を動作させている Service Account（SA）の権限を使って、kubeconfig ファイルを用意することなく認証することも可能です。これは、いわゆる In-Cluster Authentication などと呼ばれるもので、自動でマ /var/run/secrets/kubernetes.io/serviceaccount にマウントされる SA のトークンを使って認証するものです。\n今回は、Kubernetes 上の Pod として動作させる Executor として executor02（node04）を作成し、ここで In-Cluster Authentication を試します。\n設定ファイルの作成 必要なファイルを準備して、環境を整えます。実ファイル群は GitHub のリポジトリに配置 しています。\nReceptor の Executor 用の設定ファイル executor02（node04）の設定ファイルの抜粋です。\n--- - node: id: executor02 ... - tcp-peer: address: node02.example.internal:7323 ... - work-kubernetes: worktype: echo-reply-incluster authmethod: incluster namespace: receptor image: quay.io/centos/centos:stream8 command: bash params: \u0026#34;-c \u0026#39;while read -r line; do echo Reply from $(cat /etc/hostname): ${line^^}; done\u0026#39;\u0026#34; authmethod を incluster にしている点が今回のポイントです。それ以外の部分は構成を簡単にするためにシンプルにしていますが、標準入力の動作は確認できるようにしています。実際には、これまでのデモで紹介したような allowruntime* などももちろん併用可能です。\nまた、tcp-peer で指定しているホスト名には Executor の Pod からアクセスが発生するため、上位の DNS で名前解決できるようにしておきます。\nDocker Compose ファイル node04 が接続しにいく対向の node02 側では、Docker Compose ファイルでポート 7323 を外部に公開しています。\n... node02: ... ports: - 7323:7323 ... ... SA 関連のマニフェスト In-Cluster Authentication を使うと、Pod の作成は SA の権限で行われます。つまり、SA が Pod の作成に必要な権限を持っている必要があります。このため、SA だけでなく Role と Role Binding の作成も必要です。\n特別な工夫はなく、通常のお作法に従って作ればよいですが、Role には Pod の作成・削除や状態の確認のための pods への権限だけでなく、worker コンテナの標準出力を API 経由で取得するための pods/log、標準入力をコンテナに渡すための pods/attach も必要なようです。今回は以下の定義としています。\napiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: receptor rules: - apiGroups: - \u0026#34;\u0026#34; resources: - pods verbs: - get - list - watch - create - delete - apiGroups: - \u0026#34;\u0026#34; resources: - pods/log verbs: - get - apiGroups: - \u0026#34;\u0026#34; resources: - pods/attach verbs: - create また、Executor 自体の Pod のマニフェストも作成します（動作確認程度なので Deployment にはしていません）。以下の定義です。SA を指定しているほか、Receptor の設定ファイルとして ConfigMap をマウントさせています。image はここでは latest タグにしていますが、Kustomize で実際には v1.3.0 に置き換えます。\napiVersion: v1 kind: Pod metadata: namespace: receptor name: node04 spec: hostname: node04 containers: - name: node04 image: quay.io/ansible/receptor:latest command: - receptor - -c - /etc/receptor/receptor.conf volumeMounts: - name: receptor-conf mountPath: /etc/receptor/receptor.conf subPath: receptor.conf volumes: - name: receptor-conf configMap: name: receptor-conf items: - key: node04.yml path: receptor.conf restartPolicy: Always serviceAccountName: receptor その他、実際のファイルは GitHub のリポジトリ にあるので割愛して、まとめて Kustomize で必要なリソース群を作成します。\n$ kubectl apply -k . namespace/receptor created serviceaccount/receptor created role.rbac.authorization.k8s.io/receptor created rolebinding.rbac.authorization.k8s.io/receptor created configmap/receptor-conf created pod/node04 created Pod が起動しました。\n$ kubectl -n receptor get pod NAME READY STATUS RESTARTS AGE node04 1/1 Running 0 5s $ kubectl -n receptor get pod node04 -o yaml apiVersion: v1 kind: Pod metadata: ... name: node04 namespace: receptor ... spec: containers: - command: - receptor - -c - /etc/receptor/receptor.conf image: quay.io/ansible/receptor:v1.3.0 imagePullPolicy: IfNotPresent name: node04 ... volumeMounts: - mountPath: /etc/receptor/receptor.conf name: receptor-conf subPath: receptor.conf - mountPath: /var/run/secrets/kubernetes.io/serviceaccount name: kube-api-access-b2sck readOnly: true ... serviceAccount: receptor serviceAccountName: receptor ... volumes: - configMap: defaultMode: 420 items: - key: node04.yml path: receptor.conf name: receptor-conf name: receptor-conf - name: kube-api-access-b2sck projected: ... 実際の動き Docker 側でもコンテナを起動させて、動きを確認します。\n$ docker compose up -d $ docker compose exec -it node01 bash [root@node01 /]# まずは、Kubernetes 上で動作させている executor02 が Controller 側で認識され、ワーク echo-reply-incluster が使える状態であることを確認します。\n[root@node01 /]# receptorctl status ... Known Node Known Connections controller01 relayer01: 1 executor01 relayer01: 1 executor02 relayer01: 1 relayer01 controller01: 1 executor01: 1 executor02: 1 Route Via executor01 relayer01 executor02 relayer01 relayer01 relayer01 Node Service Type Last Seen Tags controller01 control Stream 2022-12-04 22:23:48 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} executor01 control Stream 2022-12-04 22:23:19 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} executor02 control Stream 2022-12-04 22:22:55 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} Node Work Types controller01 custom-runtime-pod executor01 echo-sleep, cat-files, echo-reply, custom-predefined-pod executor02 echo-reply-incluster ワークを実行します。\n[root@node01 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor02 \\ --rm \\ --follow \\ --payload - \\ echo-reply-incluster Reply from echo-reply-incluster-ql9cp: HELLO RECEPTOR! (JB484Cvb, released) Pod が意図通り作成されました。\n$ kubectl -n receptor get pod NAME READY STATUS RESTARTS AGE node04 1/1 Running 0 107s echo-reply-incluster-ql9cp 0/1 Completed 0 2s $ kubectl -n receptor get pod -o yaml ... - apiVersion: v1 kind: Pod metadata: ... generateName: echo-reply-incluster- name: echo-reply-incluster-ql9cp namespace: receptor ... spec: containers: - args: - -c - \u0026#39;while read -r line; do echo Reply from $(cat /etc/hostname): ${line^^}; done\u0026#39; command: - bash image: quay.io/centos/centos:stream8 imagePullPolicy: IfNotPresent name: worker ... Executor が Pod として動作していて、かつ SA が充分な権限を持っていれば、kubeconfig を使わなくてもワークを実行できることが確認できました。\nまとめ 前回のエントリ の続きとして、Kubernetes クラスタ上でのワークの実行を試しました。\nPod が自由に構成でき、ペイロードも渡せるので、相当に柔軟な対応ができそうです。\nReceptor 関連エントリ Receptor (1)： Receptor はじめの一歩 Receptor (2)： Kubernetes 上でのワークの実行 Receptor (3)： 暗号化と認証、ファイアウォール、電子署名 Receptor (4)： AWX と Automation Mesh での Receptor の使われ方 Receptor (5)： AWX で Hop Node 込みの Automation Mesh を強引に構成する ","date":"2022-12-05T23:55:08Z","image":"/archives/4755/img/image-342.png","permalink":"/archives/4755/","title":"Receptor (2)： Kubernetes 上でのワークの実行"},{"content":"はじめに Ansible Automation Platform（AAP）には、その構成の柔軟性を飛躍的に向上させる Automation Mesh と呼ばれる仕組みがあります。AWX でも Execution Node がサポートされ、より近いことができるようになってきました（詳細は 別のエントリ で紹介しています）。\nWhat\u0026rsquo;s new: an introduction to automation mesh Red Hat Ansible Automation Platform automation mesh guide この Automation Mesh の中核 とも呼べる技術が、Receptor です。本エントリではこの Receptor を理解するはじめの一歩として、概要と基本的な使い方と動きを確認します。\nReceptor は、Ansible 関連のプロジェクトではありますが、ざっくりとは 任意のコマンドをリモートノードで実行する仕組み であり、本来は Ansible とはまったく関係なく単体でも利用できる モノです。そのため本エントリでは、Automation Mesh や AWX、Ansible Runner などの Ansible 周辺との関係はいったん忘れて、まずは あくまで Receptor 単体に着目 します。\nなお、エントリ中で紹介する設定ファイル類やそれを実際に動作させられる Docker 用の Compse ファイルは、GitHub のリポジトリ にも配置しています。お手元で試したい方は併せてどうぞ。\n概要 Receptor（ansible/receptor）は、ひとことでは 任意のコマンドをリモートノードで安全かつ確実に実行する仕組み といえます。\nリポジトリ と ドキュメント を情報源にざっくりまとめると、次のようなことを実現できます。\nReceptor が動作しているノード同士で 他ノードへの接続情報を交換しあう ことで、全体としてひとつの メッシュネットワーク が構成され、任意のノード間で相互に疎通 できる状態が作られる メッシュネットワーク内では、あるノードから他のノードに対して、コマンドの実行や Kubernetes クラスタへの Pod の作成を指示 できる また、こうしたリモート実行の信頼性を高めるために、次のような工夫もされています。\nあるノードが停止しても、メッシュネットワークの経路が再計算され、他のノードを経由して迂回できればノード間の疎通は維持される リモート実行の動作中にノード間のネットワークが一時的に切断されても、リモート側の処理には影響せず、結果は復旧後に欠損なく取得できる ノード間の接続に、TLS による相互認証と暗号化を利用できる ノードへの処理の実行指示が改竄されていないことを、電子署名により検証できる 組み込みのファイアウォールで、特定のノード間の疎通可否を制御できる これらの特徴から、前述のとおり、Receptor は 任意のコマンドをリモートノードで安全かつ確実に実行する仕組み といえそうです。\nインストールと設定 インストール Receptor を使うためにインストールするモノには、次の二つがあります。receptorctl の導入は任意ですが、Receptor をコマンドで気軽に触る手段が現状ではこれしかないので、勉強目的ではほぼ必須です。\nReceptor 本体（必須） Receptor 専用のコマンドラインツールである receptorctl（任意） コンテナ環境で利用する場合 上記のいずれも、Receptor のコンテナイメージ（quay.io/ansible/receptor）にはあらかじめ含まれています。コンテナ環境に慣れているなら、これがもっとも手軽な入手手段です。ただし、Receptor 経由で実行できることの範囲も当然ながらコンテナ内に閉じてしまうため、実運用では注意が必要です。\nとはいえ、動作の学習には充分なので、本エントリではこれを使います。\nホストで直接動作させる場合 Receptor の本体（Go 言語製のシングルバイナリ）は、ソースコードのコンパイルか、Copr のリポジトリ上の RPM パッケージ か、前述のコンテナイメージからのコピーで導入できます。\n# ソースコードをコンパイルして導入 git clone https://github.com/ansible/receptor.git cd receptor make receptor # RPM パッケージで導入 dnf copr enable ansible-awx/receptor dnf install receptor # コンテナイメージからコピーして導入 CONTAINER_ID=$(docker create quay.io/ansible/receptor:latest) docker cp ${CONTAINER_ID}:/usr/bin/receptor . docker rm ${CONTAINER_ID} ドキュメント ではソースコードからコンパイルする手段しか紹介されていないので、それ以外の手段のサポート具合は不明です。\nが、RPM パッケージは AWX の Execution Node のインストーラでも使われていますし、コンテナイメージからのコピーも quay.io/ansible/awx-ee のビルドの過程 で使われているので、それなりに安心できる手段といえそうな気配はあります。RPM パッケージには Systemd 用のファイルや設定ファイルのサンプルなども含まれているので、用途によっては便利そうです。なお、充分なテストはしていませんが、ソースコードからコンパイルすれば Windows 上でも動作させられるようでした。\nreceptorctl も、前述の Copr のリポジトリ で RPM パッケージが公開されています。Copr を使わない場合は、PyPI から pip コマンドで導入できます。\n# RPM パッケージで導入 dnf copr enable ansible-awx/receptor dnf install receptorctl # PyPI から導入 pip install receptorctl 設定 Receptor の設定方法は次の二通りです。\nReceptor の起動時の引数ですべて指定する 設定ファイルを作成し、Receptor の起動時に設定ファイルを指定する 設定できる範囲はどちらでも変わらず、設定ファイルに書けることはすべて起動時の引数でも渡せるようです。また、両方の併用もできそうです。\n本エントリでは、一貫して設定ファイルを使った構成を行っています。設定ファイルは YAML 形式で、例えば以下のような内容です（具体的な内容は後述します）。\n--- - node: id: demo01 - log-level: level: debug - tcp-listener: port: 7323 このファイルのパスを起動時の引数 -c（--config）で指定すると、ファイルの記述通りに構成されます。\nreceptor -c /etc/receptor/receptor.yml 設定できる内容は、ドキュメント や --help で確認できます。\nreceptor --help 起動 起動すると、フォアグラウンドで動作し始めます。\n$ receptor -c /etc/receptor/receptor.yml DEBUG 2022/11/25 07:24:36 Listening on TCP [::]:7323 INFO 2022/11/25 07:24:36 Initialization complete ... RPM パッケージを使って導入した場合は、Systemd を使った起動と停止もできます。Receptor 1.3.0 に含まれる Systemd 用のファイルはこんな感じでした。\n[Unit] Description=Receptor [Service] ExecStart=/usr/bin/receptor -c /etc/receptor/receptor.conf User=receptor Group=receptor StandardOutput=append:/var/log/receptor/receptor.log Restart=on-failure [Install] WantedBy=multi-user.target 参考： ビルトインツールの利用 バイナリ receptor は、前述のように何らかの構成を指定して Receptor のノードとして起動させるのが主ですが、組み込みのツール として、CA 証明書や証明書署名要求の作成、証明書への署名が行える機能も持っています。本エントリでは紹介は割愛します。\nreceptor --cert-init commonname=... receptor --cert-makereq commonname=... receptor --cert-signreq req=... デモ (1)： ノードの接続とコマンドの実行 実際に、リモートノードでのコマンド実行をもっともシンプルな形で実践しながら、構成の仕方や動きを確認します。\n何はともあれ、まずはそもそも メッシュネットワークを構成する 必要があります。何も考えずにとにかく起動すればあとは全自動で…… というわけではなく、最終的なメッシュネットワークのトポロジはあくまで利用者の責任で設計が必要 であり、各ノードでそのための設定も必要 であることには注意が必要です。\nまた、コマンドのリモートノードでの実行についても、何も設定しなくても任意のノードから任意のノードで任意のコマンドを好き放題に実行できるわけではなく、実行を許可するコマンド を あらかじめリモートノード側で定義する 必要があります。\n構成例 基本の動きを確認したいので、まずは図の 3 ノードのシンプルな構成を考えます。\n左端のノード から、自分自身 と 右端のノード に対してコマンド cat /etc/hostname が実行できる状態を目指します。\n前提： メッシュネットワークの考え方 メッシュネットワークは、各ノードで 隣接するノード同士 の バックエンドでの相互の接続方法を設定 することで構成されます。具体的には、隣接する二つのノードについて、一方で 接続を待ち受ける設定（*-listener） を、他方で 接続しに行く設定（*-peer）を行います。\nノード同士がバックエンドで接続されると、あたかもルータが経路情報を相互にアドバタイズするときのように、自身の知る隣接ノードの情報や他ノードへの経路がノード間で交換されます。最終的には 全ノードが他の全ノードへの経路情報を持つ ようになり、任意のノード同士で双方向に疎通できるメッシュネットワークが構成されます。上図では、左端のノードと右端のノードはバックエンドでは直接の接続はありません が、経路計算により、中央のノードを経由して自由に疎通できる ようになります。\nバックエンド接続には方向がある ため、ノード間の経路に NAT やファイアウォールがある場合は構成に配慮が必要です。一方で、メッシュネットワーク上の通信はバックエンドの接続方向とは無関係に行える ため、ネットワーク環境側に制約がなければ、例えば上図の矢印を逆方向に構成してもまったく問題ないことになります。\nなお、バックエンドの接続には、現時点では TCP か UDP か WebSocket のいずれかを利用できます。本エントリでは TCP を使います。\n前提： ノードの種類 リモートノードに何らかの処理をさせるには、実行元と実行先のそれぞれのノードで構成が必要です。明確な定義はありませんが、便宜上、本エントリでは 実行元を Controller、実行先を Executor として区別します。\nController Executor への処理の実行指示 やその進捗と結果の表示、メッシュネットワークの状態の確認 を行う役割のノード Executor Controller からの指示を受け て、実際に コマンドの実行 または Kubernetes クラスタの Pod の作成 を行う役割のノード 今回の構成では、左端のノードが Controller 兼 Executor で、右端のノードが Executor です。\n設定ファイルの作成 今回の目的の構成を実現する、各ノードの実際の設定ファイルです。GitHub にも設定ファイルと Docker 用の Compose ファイルを配置 しています。\nノード controller01 の設定ファイル 左端のノードの設定ファイルの例です。Controller 兼 Executor として構成します。\n--- - node: id: controller01 - log-level: level: debug - tcp-peer: address: node02.example.internal:7323 - control-service: service: control filename: /tmp/receptor.sock - work-command: worktype: gather-hostname command: cat params: /etc/hostname 以下、部分ごとの説明です。\n- node: id: controller01 node の id は、Receptor の世界でそのノードのユニークな ID です。TCP/IP の世界のホスト名とは 別物 で、ノードごとに localhost 以外の任意の名称を付けられます。ここでは controller01 を与えています。明示しない場合はホスト名がノード名として利用されます。\n- log-level: level: debug 動きを追いやすくするため、ログレベルを debug にしています。\n- tcp-peer: address: node02.example.internal:7323 メッシュネットワークを構成するための バックエンドでの接続方法 を定義する部分です。このノードは 接続しに行く側 で、バックエンドに TCP を利用するので、**==tcp-peer==** を使って接続先である 待ち受け側のホスト名とポート番号を指定 しています。今回は、中央のノードを指定しています。後述しますが、待ち受け側では tcp-listener を使って待ち受けています。\n- control-service: service: control filename: /tmp/receptor.sock control-service は、Receptor を制御するための組み込みのサービスを起動させる設定です。そのノードが 他のノードに指示を出す場合 と 他のノードから指示を受け取る場合 の両方、つまり Controller と Executor の両方で必要 です。\nかつ、そのノードを Controller にしたい場合は filename も指定する とよいでしょう。このファイルは receptorctl からの操作で発生するソケット通信で利用します。今回は、/tmp/receptor.sock としました。なお、指定したパスにファイルが存在しない場合は、起動時に自動で作成されます。\n- work-command: worktype: gather-hostname command: cat params: /etc/hostname このノードに Executor として実行させる処理の定義（ワークタイプ）です。コマンドを実行させる なら ==work-command==、Kubernetes クラスタへ Pod を作成させる なら work-kubernetes を指定します。今回は前者です。\n実行させるコマンド（cat）とその引数（/etc/hostname）を指定し、この組み合わせの設定に worktype で gather-hostname と名前を付けています。この名前（ワークタイプ名）は後で Controller からコマンドを実行するときに利用します。\nコマンドの引数や標準入力を実行時に与える手段も用意されていますが、それについては後述します。\nノード relayer01 の設定ファイル 中央のノードの設定ファイルです。このノードは Controller でも Executor でもなく、単に左右からの通信を中継するだけなので、他のノードとの接続に必要な設定のみを行っています。\n--- - node: id: relayer01 - log-level: level: debug - tcp-listener: port: 7323 tcp-listener が、バックエンドに TCP を利用 して 待ち受ける側 としてふるまうための構成です。ポート番号に 7323 を指定しています。前述の controller01 では、このノードのこのポートを tcp-peer で指定していました。\nなお、デフォルトでは 0.0.0.0 にバインドされますが、bindaddr を指定して特定の IP アドレスでのみ待ち受けさせる構成も可能です。\nノード executor01 の設定ファイル 右端のノードの設定ファイルです。\n--- - node: id: executor01 - log-level: level: debug - tcp-peer: address: node02.example.internal:7323 - control-service: service: control - work-command: worktype: gather-hostname command: cat params: /etc/hostname このノードは、バックエンドでは 接続しにいく側 のため、tcp-peer を指定しています。接続先は中央のノードです。\nまた、Executor であり、Controller からのコマンド実行を受け付けられるよう、control-service と work-command を定義しています。このノード自体は Controller にはしないため、control-service に filename は指定していません。\nDocker Compose ファイル 今回の諸々の実験はコンテナ環境で行うため、次の Docker Compose ファイルを用意します。前述の設定ファイル群をコンテナにマウントし、Receptor の起動時に -c で指定しているだけの簡単なものです。コンテナ名とホスト名は、左端から順に node01、node02、node03 としています。\nservices: node01: image: ${RECEPTOR_IMAGE:?err} container_name: node01 hostname: node01.example.internal command: \u0026#34;receptor -c /etc/receptor/receptor.conf\u0026#34; volumes: - \u0026#34;./conf/node01.yml:/etc/receptor/receptor.conf\u0026#34; node02: image: ${RECEPTOR_IMAGE:?err} container_name: node02 hostname: node02.example.internal command: \u0026#34;receptor -c /etc/receptor/receptor.conf\u0026#34; volumes: - \u0026#34;./conf/node02.yml:/etc/receptor/receptor.conf\u0026#34; node03: image: ${RECEPTOR_IMAGE:?err} container_name: node03 hostname: node03.example.internal command: \u0026#34;receptor -c /etc/receptor/receptor.conf\u0026#34; volumes: - \u0026#34;./conf/node03.yml:/etc/receptor/receptor.conf\u0026#34; 利用するイメージ（${RECEPTOR_IMAGE}）には、次の .env ファイルを用意して今回は v1.3.0 を指定しています（追記： エントリ公開時はその時点で最新の v1.3.0.dev2 としていましたが、数日後に v1.3.0 がリリースされたので差し替えました。エントリで紹介した内容の動作は確認済みです）。\nRECEPTOR_IMAGE=\u0026#34;quay.io/ansible/receptor:v1.3.0\u0026#34; 実際の動き コンテナを起動させて、動きを観察します。\ndocker compose up -d メッシュネットワークの状態 起動後のログからは、バックエンド接続の初期化やノード間での経路交換、ノードの検出の様子が確認できます。node01（controller01）は node03（executor01）との直接のバックエンド接続を持ちませんが、node02（relayer01）経由で node03（executor01）の情報も把握できています。\n$ docker compose logs -f node01 ... node01 | DEBUG 2022/11/25 19:51:38 Running TCP peer connection node02.example.internal:27199 node01 | WARNING 2022/11/25 19:51:38 Backend connection failed (will retry): dial tcp 172.18.0.4:27199: connect: connection refused node01 | INFO 2022/11/25 19:51:38 Running control service control node01 | INFO 2022/11/25 19:51:38 Initialization complete node01 | DEBUG 2022/11/25 19:51:43 Sending initial connection message node01 | INFO 2022/11/25 19:51:43 Connection established with relayer01 node01 | DEBUG 2022/11/25 19:51:43 Stopping initial updates node01 | DEBUG 2022/11/25 19:51:43 Re-calculating routing table node01 | DEBUG 2022/11/25 19:51:43 Sending routing update INHzPHRD. Connections: relayer01(1.00) ... node01 | DEBUG 2022/11/25 19:51:43 Received routing update iArMKNrx from relayer01 via relayer01 node01 | DEBUG 2022/11/25 19:51:43 Received routing update f650xBgb from executor01 via relayer01 ... node01 | INFO 2022/11/25 19:51:43 Known Connections: node01 | INFO 2022/11/25 19:51:43 executor01: relayer01(1.00) node01 | INFO 2022/11/25 19:51:43 controller01: relayer01(1.00) node01 | INFO 2022/11/25 19:51:43 relayer01: executor01(1.00) controller01(1.00) node01 | INFO 2022/11/25 19:51:43 Routing Table: node01 | INFO 2022/11/25 19:51:43 relayer01 via relayer01 node01 | INFO 2022/11/25 19:51:43 executor01 via relayer01 ... ところで、node01 の Receptor では、Control Service をソケットファイルのパス（/tmp/receptor.sock）とともに起動させていました。このため、このノードではこのソケットファイルを介して receptorctl による Receptor の操作を行えます。\nreceptorctl で Receptor を操作する際は、ソケットファイルのパスを receptorctl の実行時の --socket か環境変数 RECEPTORCTL_SOCKET で指定します。今回利用しているコンテナイメージでは、デフォルトで環境変数 RECEPTORCTL_SOCKET に /tmp/receptor.sock が設定されており、実際にこのパスを使うように Control Service に構成したため、--socket による明示は不要です。\n以降、わかりやすいようにコンテナのシェルから操作します（docker compose exec でも同等の操作はもちろん可能です）。\n$ docker compose exec -it node01 bash [root@node01 /]# receptorctl version receptorctl 1.3.0 receptor 1.3.0 例えば、status サブコマンドでは、メッシュネットワークの全体の情報を確認できます。以下、出力例にコメントを加えたものです。\n[root@node01 /]# receptorctl status ## 自身のノード情報 Node ID: controller01 Version: 1.3.0 System CPU Count: 4 System Memory MiB: 7762 ## 自身がバックエンドで接続しているノードとその経路のコスト Connection Cost relayer01 1 ## 自身が把握できているメッシュネットワーク内の全ノードと、各ノードのバックエンドでの接続ノードおよびそのコスト Known Node Known Connections controller01 relayer01: 1 executor01 relayer01: 1 relayer01 controller01: 1 executor01: 1 ## 自身以外の他ノードへの経路 ### executor01 へも relayer01 経由で到達できることを学習できている Route Via executor01 relayer01 relayer01 relayer01 ## メッシュネットワーク内で利用できるサービスの情報 ### controller01 と executor01 で Control Service が起動している Node Service Type Last Seen Tags controller01 control Stream 2022-11-25 19:54:13 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} executor01 control Stream 2022-11-25 19:53:43 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} ## メッシュネットワーク内で実行できる処理（ワークタイプ）の名称とノード ### controller01 と executor01 で gather-hostname を実行できる Node Work Types controller01 gather-hostname executor01 gather-hostname ping サブコマンドでは、メッシュネットワーク上の任意のノードへの到達性を確認できます。executor01 とも正常に疎通できることがわかります。\n[root@node01 /]# receptorctl ping relayer01 Reply from relayer01 in 317.614µs Reply from relayer01 in 352.845µs Reply from relayer01 in 315.205µs Reply from relayer01 in 254.051µs [root@node01 /]# receptorctl ping executor01 Reply from executor01 in 451.259µs Reply from executor01 in 539.444µs Reply from executor01 in 587.212µs Reply from executor01 in 588.532µs traceroute サブコマンドでは、メッシュネットワーク上の任意のノードへ到達する経路を確認できます。executor01 には relayer01 を経由して到達していることがわかります。\n[root@node01 /]# receptorctl traceroute executor01 0: controller01 in 73.859µs 1: relayer01 in 429.427µs 2: executor01 in 694.976µs コマンドの実行と結果の確認 実際に Controller から Executor に対して処理の実行を指示します。--node で対象ノードを指定し、標準入力を与えずに実行するために --no-payload（-n）を付与して、実行する処理名（ワークタイプ名、ここでは gather-hotname）と共に work submit サブコマンドを実行します。\n[root@node01 /]# receptorctl work submit \\ --node executor01 \\ --no-payload \\ gather-hostname Result: Job Started Unit ID: xfaqU3sC Receptor の世界は、このようなリモートノードでの処理を ワーク（Work）またはワークユニット（Work Unit）と呼んでいるようです。上記コマンドは、Receptor 目線では ワークの送信 です。これにより、リモートノードにワークが送信されます。これらワークは、内部では Workceptor と呼ばれるコンポーネントによってハンドリングされます。\nデフォルトではワークは非同期的に動作するため、実行結果は実行ごとかつノードごとに付与される一意の Unit ID を使って取得する必要があります。\n実行されたワークの一覧は、work list サブコマンドで取得できます。\n[root@node01 /]# receptorctl work list { \u0026#34;xfaqU3sC\u0026#34;: { \u0026#34;Detail\u0026#34;: \u0026#34;exit status 0\u0026#34;, \u0026#34;ExtraData\u0026#34;: { \u0026#34;Expiration\u0026#34;: \u0026#34;0001-01-01T00:00:00Z\u0026#34;, \u0026#34;LocalCancelled\u0026#34;: false, \u0026#34;LocalReleased\u0026#34;: false, \u0026#34;RemoteNode\u0026#34;: \u0026#34;executor01\u0026#34;, \u0026#34;RemoteParams\u0026#34;: {}, \u0026#34;RemoteStarted\u0026#34;: true, \u0026#34;RemoteUnitID\u0026#34;: \u0026#34;wDNTsHQO\u0026#34;, \u0026#34;RemoteWorkType\u0026#34;: \u0026#34;gather-hostname\u0026#34;, \u0026#34;SignWork\u0026#34;: false, \u0026#34;TLSClient\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;State\u0026#34;: 2, \u0026#34;StateName\u0026#34;: \u0026#34;Succeeded\u0026#34;, \u0026#34;StdoutSize\u0026#34;: 24, \u0026#34;WorkType\u0026#34;: \u0026#34;remote\u0026#34; } } 結果を取得するには、work results サブコマンドに Unit ID を渡します。\n[root@node01 /]# receptorctl work results xfaqU3sC node03.example.internal ノード executor01 のホスト名、node03.example.internal が返ってきました。コマンドは controller01 から命令しましたが、実際の処理は executor01 で行われたことがわかります。\nワークの結果は消すまで残り続けるため、確認できたらリリース（削除）します。\n[root@node01 /]# receptorctl work release xfaqU3sC Released: (xfaqU3sC, released) [root@node01 /]# receptorctl work list {} ここまでが、コマンドの実行から結果を確認するまでの一連の流れです。\nなお、結果を保持しなくてよい（結果を後から確認しない）場合は、work submit 時に --rm を付与すると、完了後に自動でワークのリリースが行われるよになります。また、--follow（-f）を付与すると同期的な動作になり、ワークの実行結果が標準出力に返ってきます。気軽に実行するなら、両方を付与してしまえば、ワークも残らず結果もすぐに確認できて便利です。\n[root@node01 /]# receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --no-payload \\ gather-hostname node03.example.internal (hwhVXaJo, released) デモ (2)： コマンドへの引数と標準入力の連携 前述のデモでは、リモートノードで実行されるコマンドは cat /etc/hostname で固定でした。一方で、Receptor には次の機能が備わっています。\nリモートコマンドの引数を実行時に追加する リモートコマンドの標準入力に任意のデータを送る ここでは、この二つの使い方と結果を確認します。\n設定ファイルの作成 前述のデモのうち、node03（executor01）の設定ファイルの work-command を次のように変更します。GitHub にも実際のファイル一式を配置 しています。\n... - work-command: worktype: cat-files command: grep params: \u0026#34;-n -H \u0026#39;\u0026#39;\u0026#34; allowruntimeparams: true - work-command: worktype: echo-reply command: bash params: \u0026#34;-c \u0026#39;while read -r line; do echo Reply from $(cat /etc/hostname): ${line^^}; done\u0026#39;\u0026#34; ひとつめの work-command の cat-files では、allowruntimeparams を true にしています。これにより、実行時にコマンドの引数を 追加 できるようになります。ここでは、ベースになるコマンドを grep -n -H '' としているので、つまり、実行時には grep するファイル名を与えられるということです。\nふたつめの echo-reply では、bash の中の read で標準入力を読み取り、すべてを大文字（^^）にして echo し返すようにしています。\n実際の動き コンテナを起動して、動きを確認します。以降は、先ほどと同様にコンテナのシェルで操作します。\n$ docker compose up -d $ docker compose exec -it node01 bash [root@node01 /]# 実行時の引数の追加 work submit に --param（-a）で params=\u0026lt;追加したい引数\u0026gt; を指定すると、実行されるワークのコマンドに 追加の引数 を与えられます。次の例では、/etc/hostname /etc/os-release を引数として追加しました。\n[root@node01 /]# receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --no-payload \\ --param \u0026#34;params=/etc/hostname /etc/os-release\u0026#34; \\ cat-files /etc/hostname:1:node03.example.internal /etc/os-release:1:NAME=\u0026#34;CentOS Stream\u0026#34; /etc/os-release:2:VERSION=\u0026#34;9\u0026#34; /etc/os-release:3:ID=\u0026#34;centos\u0026#34; /etc/os-release:4:ID_LIKE=\u0026#34;rhel fedora\u0026#34; /etc/os-release:5:VERSION_ID=\u0026#34;9\u0026#34; /etc/os-release:6:PLATFORM_ID=\u0026#34;platform:el9\u0026#34; /etc/os-release:7:PRETTY_NAME=\u0026#34;CentOS Stream 9\u0026#34; /etc/os-release:8:ANSI_COLOR=\u0026#34;0;31\u0026#34; /etc/os-release:9:LOGO=\u0026#34;fedora-logo-icon\u0026#34; /etc/os-release:10:CPE_NAME=\u0026#34;cpe:/o:centos:centos:9\u0026#34; /etc/os-release:11:HOME_URL=\u0026#34;https://centos.org/\u0026#34; /etc/os-release:12:BUG_REPORT_URL=\u0026#34;https://bugzilla.redhat.com/\u0026#34; /etc/os-release:13:REDHAT_SUPPORT_PRODUCT=\u0026#34;Red Hat Enterprise Linux 9\u0026#34; /etc/os-release:14:REDHAT_SUPPORT_PRODUCT_VERSION=\u0026#34;CentOS Stream\u0026#34; (PPLqDD0a, released) これにより、executor01 側では次のコマンドが実行されています。上記結果からも、引数で指定したファイルが grep されていることが判断できます。\ngrep -n -H \u0026#39;\u0026#39; /etc/hostname /etc/os-release 簡略化した方法として、追加したい引数はワークタイプ名の後にベタ書きも可能です（文字列によっては receptorctl に対するオプションと混同されてエラーになるため、-- に続けて表記するのがおすすめです）。\n[root@node01 /]# receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --no-payload \\ cat-files -- /etc/hostname /etc/os-release /etc/hostname:1:node03.example.internal /etc/os-release:1:NAME=\u0026#34;CentOS Stream\u0026#34; ... (WzjhPLJ0, released) 実行時の標準入力の利用 引数ではなく 標準入力 を与えることも可能です。work submit に --payload（-p）または --payload-literal（-l）でファイルや標準入力、文字列を与えると、実行されるワークのコマンドに標準入力として渡されます。\n任意のファイルの中身を渡すには、--payload（-p）でファイル名を渡します。\n[root@node01 /]# echo \u0026#34;Hello Receptor!\u0026#34; \u0026gt; /tmp/input.txt [root@node01 /]# receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --payload /tmp/input.txt \\ echo-reply Reply from node03.example.internal: HELLO RECEPTOR! (vfhNJ5UQ, released) これにより、executor01 側では、次のようなコマンドに相当する処理が実行されています（実際に echo で入力が渡されているわけではないので、あくまで疑似的な表現です）。\necho \u0026#34;Hello Receptor!\u0026#34; | bash -c \u0026#39;while read -r line; do echo Reply from $(cat /etc/hostname): ${line^^}; done\u0026#39; receptorctl への標準入力をそのままペイロードとして渡すことも可能です。この場合、--payload に - を指定します。\n[root@node01 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --payload - \\ echo-reply Reply from node03.example.internal: HELLO RECEPTOR! (NX9JQHC9, released) なお、この場合、リモートノードでのワークの開始 は receptorctl への 標準入力へのデータストリームが閉じてから である点に注意が必要です。例えば次のように 3 秒かけて 3 行の echo を行って receptorctl に渡した場合、リモートノードでのワークは、3 秒後に 3 行のテキストがまとめてペイロードとして送られてから開始 されます。\nfor i in $(seq 1 3); do echo \u0026#34;Hello Receptor!\u0026#34; sleep 1 done | receptorctl ... つまり、標準出力のストリームを開きっぱなし にして データを逐次送信する（リモート側で逐次処理させる） ようなものは、Receptor は向きません。\n任意の文字列をベタ打ちで渡すには、--payload-literal（-l）を利用します。\n[root@node01 /]# receptorctl work submit \\ --node executor01 \\ --rm \\ --follow \\ --payload-literal \u0026#34;Hello Receptor!\u0026#34; \\ echo-reply Reply from node03.example.internal: HELLO RECEPTOR! (Xs3Q5LDU, released) デモ (3)： ネットワーク障害やノード障害への耐性 ここまでで、Receptor により、リモートノードで任意のコマンドを任意のパラメータと任意のペイロードと共に実行できることを確認しました。ここでは、そうしたリモートノードでのコマンド実行がより確実に実行される仕組みとして、Receptor の耐障害性を実際に確認します。\n前提： データの授受用の一時ファイル Receptor は、技術的には、リモートノードのコマンドに 標準入力を送信 し、実行結果としてリモートノードからコマンドの 標準出力を受信 しているといえます。\nここで、標準入力と標準出力 を 確実にノード間で授受できるようにする ため、あるワークをリモートノードで実行するときには Controller のノードと Executor のノードの 両方 で、標準出力と標準入力は次の一時ファイルとして保存されます。パスはデフォルトでは /tmp/receptor/\u0026lt;ノード ID\u0026gt;/\u0026lt;ユニット ID\u0026gt;/ 配下です。\nstdin Controller 側で入力されたペイロードのコピー Executor 側にも送信され保存される stdout Executor 側のワークの実行結果（標準出力）のコピー Controller 側にも送信され保存される status ワークの実行時のパラメータや状態などを記録したファイル Controller 側と Executor 側のそれぞれで、自ノードでのワークの状態が保存される ワークが完了してリリースされると、これらの一時ファイル群は削除されます。\nこれらの授受と保存のタイミングは、ドキュメントの図で紹介されています。引用します。\nWorkceptor\rユーザから work submit に与えられたペイロードは、まず Controller 側に stdin として保存されます。その後、ワークの実行命令とともにその中身が Executor 側へも送られ、Executor 側でも stdin として保存されます。ワークのコマンドが実際に Executor 側で実行されるのは、この後です。\nExecutor 側では、ワークのコマンドは Receptor のサブプロセスとして実行されます。このサブプロセスの標準入力には Executor 側の stdin が渡され、標準出力と標準エラー出力には同じく Executor 側の stdout が割り当てられます（技術的には Go 言語の os/exec で Cmd.Std* に std* ファイル群がそのまま渡されています）。また、ワークの実行状況は status として保存され、完了まで定期的に更新されていきます。\nController 側は、ワークが完了するまで定期的に Executor 側に status を問い合わせ、リモートの stdout に更新があれば（報告された stdout のサイズが手元の stdout のサイズより大きければ）差分を受け取って Controller 側の status と stdout を更新します。\nこのように、標準入出力を一時的にファイルとして永続化することで、長時間のワークの実行中に経路や Receptor それ自体に障害が発生しても、復旧後にデータの授受を欠損なく再開できるように工夫されています。\n設定ファイルの作成 経路を冗長化するため、今回は次の 4 ノードで構成します。完全な構成ファイル群は GitHub に置いています。\ncontroller01 から executor01 への経路を 2 つにするため、Controller と Executor では tcp-peer を次のように 2 つ並べています。また、relayer01 を優先して利用させるため、経路のコストを relayer01 が 1.0、relayer02 が 2.0 としました。\n--- - node: id: controller01 ... - tcp-peer: address: node02.example.internal:7323 cost: 1.0 - tcp-peer: address: node03.example.internal:7323 cost: 2.0 ... relayer0* 側でも、次のように tcp-listener でコストを指定します。両端（*-peer と *-listener）でコストの設定が一致していなければならない点には注意が必要です。\n--- - node: id: relayer01 ... - tcp-listener: port: 7323 cost: 1.0 --- - node: id: relayer02 ... - tcp-listener: port: 7323 cost: 2.0 Executor の node04（executor01）では、次のワークタイプを構成しました。\n... - work-command: worktype: echo-reply command: bash params: \u0026#34;-c \u0026#39;while read -r line; do echo Reply from $(cat /etc/hostname): ${line^^}; done\u0026#39;\u0026#34; - work-command: worktype: echo-sleep command: bash params: \u0026#34;-c \u0026#39;for i in $(seq 1 12); do echo ${i}: $(date +%T); sleep 5; done\u0026#39;\u0026#34; echo-reply は以前のデモで利用したものそのままで、echo-sleep は 5 秒の sleep を 12 回行うワークです。\n実際の動き コンテナを起動して、動きを確認します。\ndocker compose up -d メッシュネットワークの状態からは、コストが認識されていることと、executor01 への経路が relayer01 になっていることが確認できます。\n[root@node01 /]# receptorctl status ... Connection Cost relayer01 1 relayer02 2 ... Route Via executor01 relayer01 relayer01 relayer01 relayer02 relayer02 [root@node01 /]# receptorctl traceroute executor01 0: controller01 in 70.855µs 1: relayer01 in 344.99µs 2: executor01 in 330.43µs 正常ケース ワーク echo-reply を実行して、完了を確認します。\n[root@node01 /]# echo \u0026#34;Hello Receptor!\u0026#34; | receptorctl work submit \\ --node executor01 \\ --payload - \\ echo-reply Result: Job Started Unit ID: dcyJmLNy [root@node01 /]# receptorctl work list --unit_id dcyJmLNy { \u0026#34;dcyJmLNy\u0026#34;: { \u0026#34;Detail\u0026#34;: \u0026#34;exit status 0\u0026#34;, \u0026#34;ExtraData\u0026#34;: { \u0026#34;Expiration\u0026#34;: \u0026#34;0001-01-01T00:00:00Z\u0026#34;, \u0026#34;LocalCancelled\u0026#34;: false, \u0026#34;LocalReleased\u0026#34;: false, \u0026#34;RemoteNode\u0026#34;: \u0026#34;executor01\u0026#34;, \u0026#34;RemoteParams\u0026#34;: {}, \u0026#34;RemoteStarted\u0026#34;: true, \u0026#34;RemoteUnitID\u0026#34;: \u0026#34;gfxFWsHC\u0026#34;, \u0026#34;RemoteWorkType\u0026#34;: \u0026#34;echo-reply\u0026#34;, \u0026#34;SignWork\u0026#34;: false, \u0026#34;TLSClient\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;State\u0026#34;: 2, \u0026#34;StateName\u0026#34;: \u0026#34;Succeeded\u0026#34;, \u0026#34;StdoutSize\u0026#34;: 52, \u0026#34;WorkType\u0026#34;: \u0026#34;remote\u0026#34; } } node01 と node04 で、一時ファイルの様子を確認します（以下、status は整形しています）。node04 側での Unit ID は、node01 の status に含まれる RemoteUnitID で確認できます。\n[root@node01 /]# cat /tmp/receptor/controller01/dcyJmLNy/status { \u0026#34;State\u0026#34;: 2, \u0026#34;Detail\u0026#34;: \u0026#34;exit status 0\u0026#34;, \u0026#34;StdoutSize\u0026#34;: 52, \u0026#34;WorkType\u0026#34;: \u0026#34;remote\u0026#34;, \u0026#34;ExtraData\u0026#34;: { \u0026#34;RemoteNode\u0026#34;: \u0026#34;executor01\u0026#34;, \u0026#34;RemoteWorkType\u0026#34;: \u0026#34;echo-reply\u0026#34;, \u0026#34;RemoteParams\u0026#34;: {}, \u0026#34;RemoteUnitID\u0026#34;: \u0026#34;gfxFWsHC\u0026#34;, \u0026#34;RemoteStarted\u0026#34;: true, \u0026#34;LocalCancelled\u0026#34;: false, \u0026#34;LocalReleased\u0026#34;: false, \u0026#34;SignWork\u0026#34;: false, \u0026#34;TLSClient\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;Expiration\u0026#34;: \u0026#34;0001-01-01T00:00:00Z\u0026#34; } } [root@node01 /]# cat /tmp/receptor/controller01/dcyJmLNy/stdin Hello Receptor! [root@node01 /]# cat /tmp/receptor/controller01/dcyJmLNy/stdout Reply from node04.example.internal: HELLO RECEPTOR! [root@node04 /]# cat /tmp/receptor/executor01/gfxFWsHC/status { \u0026#34;State\u0026#34;: 2, \u0026#34;Detail\u0026#34;: \u0026#34;exit status 0\u0026#34;, \u0026#34;StdoutSize\u0026#34;: 52, \u0026#34;WorkType\u0026#34;: \u0026#34;echo-reply\u0026#34;, \u0026#34;ExtraData\u0026#34;: null } [root@node04 /]# cat /tmp/receptor/executor01/gfxFWsHC/stdin Hello Receptor! [root@node04 /]# cat /tmp/receptor/executor01/gfxFWsHC/stdout Reply from node04.example.internal: HELLO RECEPTOR! ワークの状況や授受された標準入出力が、両ノードで一時ファイルとして保存されていることが確認できました。ワークをリリースすると、自ノードとリモートノードの両方でこれらのファイルが削除されます。\n[root@node01 /]# receptorctl work release --all Released: (dcyJmLNy, released) [root@node01 /]# ls -l /tmp/receptor/controller01/ total 0 なお、何らかの理由でリモートノードと疎通できない状態でリリースしたい場合は、work release に --force オプションを付与すると、自ノードのみの削除が行われます。\n冗長化された経路の障害 現在の構成では、controller01 は executor01 へ relayer01 経由で疎通しています。ここで、実行に 1 分かかるワーク echo-sleep の実行中に、経路の relayer01 を停止させて、ワークの動きを確認します。\nwork submit を実行してから、ワークが完了する前に relayer01 である node02 を停止させます。\n[root@node01 /]# receptorctl work submit \\ --node executor01 \\ --no-payload \\ echo-sleep Result: Job Started Unit ID: uocGYk24 $ docker compose stop node02 [+] Running 1/1 ⠿ Container node02 Stopped 0.1s node01 のログを確認すると、node02 への接続が失われ、経路の再計算が走ったことが確認できます。\n$ docker compose logs -f node01 ... node01 | WARNING 2022/11/25 21:37:38 Backend connection exited (will retry) ... node01 | DEBUG 2022/11/25 21:37:38 Re-calculating routing table node01 | INFO 2022/11/25 21:37:38 Known Connections: node01 | INFO 2022/11/25 21:37:38 controller01: relayer02(2.00) node01 | INFO 2022/11/25 21:37:38 relayer02: controller01(2.00) executor01(2.00) node01 | INFO 2022/11/25 21:37:38 executor01: relayer02(2.00) node01 | INFO 2022/11/25 21:37:38 relayer01: node01 | INFO 2022/11/25 21:37:38 Routing Table: node01 | INFO 2022/11/25 21:37:38 relayer02 via relayer02 node01 | INFO 2022/11/25 21:37:38 executor01 via relayer02 ... node01 | WARNING 2022/11/25 21:37:43 Backend connection failed (will retry): dial tcp: lookup node02.example.internal on 127.0.0.11:53: no such host ... node01 | WARNING 2022/11/25 21:37:51 Backend connection failed (will retry): dial tcp: lookup node02.example.internal on 127.0.0.11:53: no such host ... メッシュネットワークの状態からは、executor01 への経路が relayer02 経由に切り替わったことが確認できます。\n[root@node01 /]# receptorctl status ... Connection Cost relayer02 2 Known Node Known Connections controller01 relayer02: 2 executor01 relayer02: 2 relayer01 relayer02 controller01: 2 executor01: 2 Route Via executor01 relayer02 relayer02 relayer02 ... [root@node01 /]# receptorctl traceroute executor01 0: controller01 in 93.198µs 1: relayer02 in 308.564µs 2: executor01 in 336.695µs この状況でも、ワークは正常に完了しており、結果も欠損なく取得できます。\n[root@node01 /]# receptorctl work list --unit_id uocGYk24 { \u0026#34;uocGYk24\u0026#34;: { ... \u0026#34;StateName\u0026#34;: \u0026#34;Succeeded\u0026#34;, ... } } [root@node01 /]# receptorctl work results uocGYk24 1: 21:37:32 2: 21:37:37 ... 11: 21:38:23 12: 21:38:28 停止させていた node02 を起動させると経路の再計算が走り、controller01 から executor01 への経路が（コスト値に従って）元の relayer01 に戻ったことがわかります。\n$ docker compose start node02 [+] Running 1/1 ⠿ Container node02 Started 0.3s [root@node01 /]# receptorctl status ... Connection Cost relayer02 2 relayer01 1 Known Node Known Connections controller01 relayer01: 1 relayer02: 2 executor01 relayer01: 1 relayer02: 2 relayer01 controller01: 1 executor01: 1 relayer02 controller01: 2 executor01: 2 Route Via executor01 relayer01 relayer01 relayer01 relayer02 relayer02 ... [root@node01 /]# receptorctl traceroute executor01 0: controller01 in 58.766µs 1: relayer01 in 305.701µs 2: executor01 in 349.324µs 経路が冗長化されている場合 は、経路の障害時 には 別の経路を使ってデータの授受が正常に続行される ことが確認できました。\nワークはリリースしておきます。\n[root@node01 /]# receptorctl work release --all Released: (uocGYk24, released) 経路の全断 続いて、経路が全断 した場合の動きを確認します。\nワーク echo-sleep の実行中に、経路の relayer01 と relayer02（node02 と node03）を両方を停止させます。\n[root@node01 /]# receptorctl work submit \\ --node executor01 \\ --no-payload \\ echo-sleep Result: Job Started Unit ID: GD8cOc0c $ docker compose stop node02 [+] Running 1/1 ⠿ Container node02 Stopped 0.1s $ docker compose stop node03 [+] Running 1/1 ⠿ Container node03 Stopped 0.1s controller01 の Known Connections がなくなり、経路の表も出力されなくなりました。controller01 から executor01 への経路はすべて失われています。\n[root@node01 /]# receptorctl status ... Known Node Known Connections controller01 executor01 relayer02: 2 relayer01 relayer02 executor01: 2 ... ここで、controller01 と executor01 で、ワークの状況と一時ファイルの状況を確認します。\ncontroller01 では、実行中に executor01 への経路が全断したため、ワークのステータスが Running のままで、stdout の中身も途中で止まっています。\n[root@node01 /]# receptorctl work list --unit_id GD8cOc0c { \u0026#34;GD8cOc0c\u0026#34;: { \u0026#34;Detail\u0026#34;: \u0026#34;Running: PID 83\u0026#34;, \u0026#34;ExtraData\u0026#34;: { ... \u0026#34;RemoteUnitID\u0026#34;: \u0026#34;2Uf3Cbjj\u0026#34;, ... }, \u0026#34;State\u0026#34;: 1, \u0026#34;StateName\u0026#34;: \u0026#34;Running\u0026#34;, \u0026#34;StdoutSize\u0026#34;: 24, ... } } [root@node01 /]# cat /tmp/receptor/controller01/GD8cOc0c/status { \u0026#34;State\u0026#34;: 1, \u0026#34;Detail\u0026#34;:\u0026#34;Running: PID 83\u0026#34;, \u0026#34;StdoutSize\u0026#34;:24, ... \u0026#34;ExtraData\u0026#34;: { ... \u0026#34;RemoteUnitID\u0026#34;: \u0026#34;2Uf3Cbjj\u0026#34;, ... } } [root@node01 /]# cat /tmp/receptor/controller01/GD8cOc0c/stdout 1: 11:31:18 2: 11:31:23 一方で、executor01 側では、すでに処理が完了しているようです。\n[root@node04 /]# cat /tmp/receptor/executor01/2Uf3Cbjj/status { \u0026#34;State\u0026#34;: 2, \u0026#34;Detail\u0026#34;: \u0026#34;exit status 0\u0026#34;, \u0026#34;StdoutSize\u0026#34;: 147, ... } [root@node04 /]# cat /tmp/receptor/executor01/2Uf3Cbjj/stdout 1: 11:31:18 2: 11:31:23 ... 11: 11:32:09 12: 11:32:14 ワークの実処理は Executor 側に閉じて行われる ため、その成否に Controller との接続性はまったく関係がありません。したがって、処理中に Controller と Executor の間の経路が全断しようとも、Executor はそのまま淡々と処理を続行し、ワークを正常に完了できます。\nここで、経路の node02 を復旧させます。\n$ docker compose start node02 [+] Running 1/1 ⠿ Container node02 Started 0.3s Controller から Executor への経路が再計算により復活しました。ワークも Running から Succeeded になり、結果も正常に欠損なく Controller 側で取得できます。\n[root@node01 /]# receptorctl status ... Connection Cost relayer01 1 Known Node Known Connections controller01 relayer01: 1 executor01 relayer01: 1 relayer01 controller01: 1 executor01: 1 relayer02 Route Via executor01 relayer01 relayer01 relayer01 ... [root@node01 /]# receptorctl work list --unit_id GD8cOc0c { \u0026#34;GD8cOc0c\u0026#34;: { \u0026#34;Detail\u0026#34;: \u0026#34;exit status 0\u0026#34;, ... \u0026#34;StateName\u0026#34;: \u0026#34;Succeeded\u0026#34;, \u0026#34;StdoutSize\u0026#34;: 147, ... } } [root@node01 /]# receptorctl work results GD8cOc0c 1: 11:31:18 2: 11:31:23 ... 11: 11:32:09 12: 11:32:14 ワークがいちど送信できてしまえば、Controller と疎通できなくても Executor のその後の実処理には影響しない こと、Executor 側で一時ファイルに標準出力が保存されている ことで Controller 側は後からでも結果を欠損なく受領できる ことが確認できました。\nnode03 も復旧させれば、完全に元通りです。ワークもリリースしておきます。\n$ docker compose start node03 [+] Running 1/1 ⠿ Container node03 Started 0.3s [root@node01 /]# receptorctl work release --all Released: (GD8cOc0c, released) Controller ノードの障害 続いて、Controller ノードで障害 が発生したパタンを確認します。ワーク echo-sleep の実行中に、今度は contoller01（node01）を停止させます。\n[root@node01 /]# receptorctl work submit \\ --node executor01 \\ --no-payload \\ echo-sleep Result: Job Started Unit ID: zna1urHX $ docker compose stop node01 [+] Running 1/1 ⠿ Container node01 Stopped 0.1s Executor である node04 で一時ファイルの様子を確認すると、ワークは正常に完了したようです。先ほどの経路の全断の例と同様、Controller と疎通できなくても Executor の実処理には影響しない ことがわかります。\n[root@node04 /]# cat /tmp/receptor/executor01/nJO9PdbS/status { \u0026#34;State\u0026#34;: 2, \u0026#34;Detail\u0026#34;: \u0026#34;exit status 0\u0026#34;, \u0026#34;StdoutSize\u0026#34;: 147, ... } ここで、Controller である node01 を再開させます。\n$ docker compose start node01 [+] Running 1/1 ⠿ Container node02 Started 0.3s Controller からジョブの結果を確認できました。正常に完了したことが認識できており、標準出力も全量が取得できているようです。\n[root@node01 /]# receptorctl work list { \u0026#34;zna1urHX\u0026#34;: { \u0026#34;Detail\u0026#34;: \u0026#34;exit status 0\u0026#34;, \u0026#34;ExtraData\u0026#34;: { ... \u0026#34;RemoteUnitID\u0026#34;: \u0026#34;nJO9PdbS\u0026#34;, ... }, \u0026#34;State\u0026#34;: 2, \u0026#34;StateName\u0026#34;: \u0026#34;Succeeded\u0026#34;, \u0026#34;StdoutSize\u0026#34;: 147, ... } } ワークの状態や経過が Controller 側でも一時ファイルとして永続化される ことで、Controller の Receptor が再起動 しても ワークを見失うことなく復旧される ことが確認できました。\nワークはリリースしておきます。\n[root@node01 /]# receptorctl work release --all Released: (zna1urHX, released) Executor ノードの障害 最後に、Executor ノードで障害が発生したパタンを確認します。ワーク echo-sleep の実行中に、executor01（node04）を停止させます。\n[root@node01 /]# receptorctl work submit \\ --node executor01 \\ --no-payload \\ echo-sleep Result: Job Started Unit ID: 5lIdpCah $ docker compose stop node04 [+] Running 1/1 ⠿ Container node01 Stopped 0.1s Controller 側では、ワークはまだ Running のままです。標準出力も途中までしか取得できていません。\n[root@node01 /]# receptorctl work list { \u0026#34;5lIdpCah\u0026#34;: { \u0026#34;Detail\u0026#34;: \u0026#34;Running: PID 182\u0026#34;, \u0026#34;ExtraData\u0026#34;: { ... \u0026#34;RemoteUnitID\u0026#34;: \u0026#34;62ESTRMU\u0026#34;, ... }, \u0026#34;State\u0026#34;: 1, \u0026#34;StateName\u0026#34;: \u0026#34;Running\u0026#34;, \u0026#34;StdoutSize\u0026#34;: 96, ... } } node04 を再開させても、状態は変わりません。node04 側の status ファイルも Running のままです。\n$ docker compose start node04 [+] Running 1/1 ⠿ Container node04 Started 0.3s [root@node01 /]# receptorctl work list { \u0026#34;5lIdpCah\u0026#34;: { \u0026#34;Detail\u0026#34;: \u0026#34;Running: PID 182\u0026#34;, \u0026#34;ExtraData\u0026#34;: { ... \u0026#34;RemoteUnitID\u0026#34;: \u0026#34;62ESTRMU\u0026#34;, ... }, \u0026#34;State\u0026#34;: 1, \u0026#34;StateName\u0026#34;: \u0026#34;Running\u0026#34;, \u0026#34;StdoutSize\u0026#34;: 96, ... } } [root@node04 /]# cat /tmp/receptor/executor01/62ESTRMU/status { \u0026#34;State\u0026#34;: 1, \u0026#34;Detail\u0026#34;: \u0026#34;Running: PID 182\u0026#34;, ... } 今回は、node04 をコンテナごと停止させたので、ワークの実処理として起動された bash も止まっており、PID 182 はもう存在しません。Receptor だけが停止 したのであれば状況の継続的な追跡ができるようですが、ワークの実処理ごと停止 した場合は、追跡できなくなるようです。\nとはいえ、ワークのコマンドの冪等性は当然ながら Receptor 側からは知りえない ので、むやみに再実行されるよりは、何もしないで止まったままの方が安全 ではあります。\nワークをリリースします。\n[root@node01 /]# receptorctl work release --all Released: (5lIdpCah, released) 個人的には、このパタンではワーク自体が Failed で落ちたほうが素直な気もしていますが、ソースコードをみても、少なくとも現時点の実装だとこれが仕様といえそうです。気になるので Issue を作って PR も送っています。\nデモ (4)： 複雑なトポロジの例 これまでのデモの構成は非常にシンプルでしたが、*-listerner と *-peer は 複数回かつ両方 記述できるため、1:N、N:1、N:N の関係も自由に定義できます。次のような複雑なトポロジでも、必要なことはあくまで隣接するノードの *-listerner と *-peer の定義だけです。実際のファイル群は GitHub に配置 しています。\n経路情報の交換が完了すれば、全ノードが他の全ノードと双方向に疎通できるようになります。\n[root@wc01 /]# receptorctl status Node ID: west-controller01 Version: 1.3.0 System CPU Count: 4 System Memory MiB: 7762 Connection Cost west-relayer03 1 Known Node Known Connections controller01 east-relayer01: 1 east-relayer02: 1 west-relayer01: 1 west-relayer02: 1 east-executor01 east-relayer01: 1 east-relayer02: 1 east-relayer01 controller01: 1 east-executor01: 1 east-relayer02 controller01: 1 east-executor01: 1 west-controller01 west-relayer03: 1 west-executor01 west-relayer03: 1 west-executor02 west-relayer01: 1 west-relayer02: 1 west-relayer01 controller01: 1 west-executor02: 1 west-relayer03: 1 west-relayer02 controller01: 1 west-executor02: 1 west-relayer03 west-controller01: 1 west-executor01: 1 west-relayer01: 1 Route Via controller01 west-relayer03 east-executor01 west-relayer03 east-relayer01 west-relayer03 east-relayer02 west-relayer03 west-executor01 west-relayer03 west-executor02 west-relayer03 west-relayer01 west-relayer03 west-relayer02 west-relayer03 west-relayer03 west-relayer03 Node Service Type Last Seen Tags west-controller01 control Stream 2022-11-26 20:12:30 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} east-executor01 control Stream 2022-11-26 20:11:35 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} west-executor01 control Stream 2022-11-26 20:11:36 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} west-executor02 control Stream 2022-11-26 20:11:36 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} controller01 control Stream 2022-11-26 20:11:36 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} Node Work Types east-executor01 gather-hostname west-executor01 gather-hostname west-executor02 gather-hostname 左上の Controller wc01 から右端の Executor ee01 への疎通を確認し、ワークを送っている例です。\n[root@wc01 /]# receptorctl ping east-executor01 Reply from east-executor01 in 1.414839ms Reply from east-executor01 in 1.977049ms Reply from east-executor01 in 1.366458ms Reply from east-executor01 in 1.9261ms [root@wc01 /]# receptorctl traceroute east-executor01 0: west-controller01 in 96.69µs 1: west-relayer03 in 317.221µs 2: west-relayer01 in 742.736µs 3: controller01 in 839.477µs 4: east-relayer02 in 1.204882ms 5: east-executor01 in 806.648µs [root@wc01 /]# receptorctl work submit \\ --node east-executor01 \\ --rm \\ --follow \\ --no-payload \\ gather-hostname ee01.example.internal (7W9RAP7A, released) まとめ Automation Mesh のコア要素である Receptor の基本的な使い方と動きを確認しました。\nReceptor にはこれ以外にも Kubernetes クラスタへの Pod 作成、暗号化や組み込みのファイアウォール など、さまざまな機能があります。後続のエントリで順次簡単に紹介しています。\nReceptor 関連エントリ Receptor (1)： Receptor はじめの一歩 Receptor (2)： Kubernetes 上でのワークの実行 Receptor (3)： 暗号化と認証、ファイアウォール、電子署名 Receptor (4)： AWX と Automation Mesh での Receptor の使われ方 Receptor (5)： AWX で Hop Node 込みの Automation Mesh を強引に構成する ","date":"2022-12-03T01:10:23Z","image":"/archives/4605/img/image-342.png","permalink":"/archives/4605/","title":"Receptor (1)： Receptor はじめの一歩"},{"content":"はじめに vSphere 8 で vSphere vMotion Notifications と呼ばれる機能が追加されました。\nvSphere vMotion Notifications | VMware Virtual Machine Conditions and Limitations for vSphere vMotion ざっくりとは、 vMotion に耐えられないアプリケーションが動作している VM 向け の機能で、ゲスト OS に vMotion の事前準備と事後処理を行う機会を与える ものです。ESXi は、vMotion がトリガされると対象のゲスト OS に通知し、その通知に対するゲスト OS からの応答（Acknowledgement）が返ってくるまで実際の移行処理を遅延させます。ゲスト OS 目線では、通知を受けたら必要な事前準備を行い、その後に応答を返すことで、vMotion に安全に備えられることになります。その後の移行処理の完了も別途通知を受け取れるため、事後処理も行えます。\nこの仕組みにより、厳しい動作要件があって vMotion に耐えられなかったアプリケーションも、vMotion を乗り越えられるようになる…… ということのようです。例えば、複数ノードでクラスタ化されているアプリケーションで、vMotion の前後でクラスタからの離脱と再参加を自動で行うようなイメージですね。\n自宅に何かそういう厳しいアプリケーションがあるわけではないのですが、おもしろそうな機能なので実際に触ってみました。本エントリでは実際の設定や動作を紹介します。\n基本的な動き vSphere vMotion Notifications のおおまかな動きは次の通りです。\nvCenter Server で vMotion がトリガされると、ESXi は 対象の VM で動作する VMware Tools（または Open VM Tools）に vMotion の開始を通知 する ESXi は その通知に対する応答（Acknowledgement）が返ってくるまで実際の移行処理を遅延 させる ESXi は応答が返ってきたら移行処理を行い、vMotion が終了したら 終了も通知 する vSphere 側の責任範囲はこれだけなので、つまり、通知を受け取ってからのゲスト OS 内の処理 は 利用者の責任で実装が必要 ということになります。具体的には次のような処理です。\n通知の有無を定期的に確認する処理 vMotion の開始の通知を受けて、アプリケーション向けに必要な事前準備を行う処理 事前準備が完了してから応答（Acknowledgement）を返す処理 vMotion の終了の通知を受けて、アプリケーション向けに必要な事後処理を行う処理 なお、vMotion がいちどトリガされると、ESXi は ゲスト OS からの応答がなくてもタイムアウトを迎えると移行処理を強制的に開始します（つまり、ゲスト OS 側から vMotion を中止する手段はありません）。このときのタイムアウト値は ESXi ホストと VM の二か所 で設定でき、両方で設定されている場合は 値が小さいほうが採用 されます。当然ながら、具体的な設定値は、ゲスト OS 内での事前準備に必要な時間を基に利用者側で検討が必要です。\n動作要件 ドキュメントにも一部記載がありますが、動作要件は以下のようです。\nvSphere 8.0 以降 仮想ハードウェアバージョン 20 以降 VMware Tools（または Open VM Tools）11.0 以降 条件を満たしていれば、ゲスト OS は Linux でも Windows でも動作 しました。\nvSphere vMotion Notifications の有効化 vSphere vMotion Notifications を利用するには、VM ごとの設定 に加えて、その VM が動作しうる ESXi ホストごとにも構成が必要 なようです（ドキュメントの表現では ESXi ごとの設定が必須であるようには読めませんでしたが、実際に試した範囲では必須っぽかったです）。\n必要な作業は次の通りです。\n対象 VM が動作しうる 全ての ESXi ホスト にタイムアウト値（VmOpNotificationToApp.Timeout）を設定する（必須） 対象 VM に通知を有効化する設定（vmOpNotificationToAppEnabled）を入れる（必須） 対象 VM に VM ごとのタイムアウト値（vmOpNotificationTimeout）を設定する（任意） 設定変更の手段 本エントリ執筆時点では、PowerCLI や PyVmomi や Govc の vSphere 8 のサポートは充分ではなく、前述の設定は残念ながらなかなか素直には変更できません。vCenter Server の Managed Object Browser を使うのが確実ですが、気軽に触るにはツラいので、今回は PowerShell 用のコマンドレットを作成しました。GitHub のリポジトリ に配置しています。\n事前準備として、スクリプトモジュールをダウンロードしてインポートし、環境変数で vCenter Server の情報を設定します。vSphere Web Service API（WS API）を SOAP で直接叩いているので、PowerCLI は不要です。\n# スクリプトモジュールのダウンロード $url = \u0026#34;https://raw.githubusercontent.com/kurokobo/vmotion-notifications-poc/main/helper/VmOpNotification.psm1\u0026#34; $file = \u0026#34;$env:Temp\\VmOpNotification.psm1\u0026#34; (New-Object System.Net.WebClient).DownloadFile($url, $file) # スクリプトモジュールのインポート Import-Module $file # 接続先と認証情報を示す環境変数の設定 $env:VMWARE_HOST = \u0026#34;vcsa.example.com\u0026#34; $env:VMWARE_USER = \u0026#34;vmotion@vsphere.local\u0026#34; $env:VMWARE_PASSWORD = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($(Read-Host \u0026#34;Password\u0026#34; -AsSecureString))) # (任意) vCenter Server の SSL 証明書の検証を無効にする設定 [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy 近いうちに PowerCLI でも PyVmomi でも Govc でも vSphere 8 対応は進むと思うので、早々に要らないスクリプトモジュールになりそうですが、取り急ぎ。\nESXi ホストの設定 対象 VM が動作しうる全 ESXi ホストで、タイムアウト値（VmOpNotificationToApp.Timeout）を設定します。単位は 秒 です。\nなお、デフォルトで 1800 が設定されているように見えますが、このままだと実際には `` として扱われるようなので、変更は必須です（仕様かバグかわかりませんが、少なくとも 8.0 の IA / GA リリースではそのような動きでした）。\n# 現在のタイムアウト値の確認 Get-VMHostVmOpNotification -Name \u0026lt;HOST_NAME\u0026gt; # タイムアウト値の設定 (VmOpNotificationToApp.Timeout = \u0026lt;VALUE\u0026gt;) Set-VMHostVmOpNotification -Name \u0026lt;HOST_NAME\u0026gt; -Timeout \u0026lt;VALUE\u0026gt; vMotion の移行元と移行先の ESXi ホストでタイムアウト値が違う場合は、動きを見る限りは 移行元 の値が採用されるようです。\nVM の設定 vSphere vMotion Notifications は、VM 単位で明示的な有効化が必要です。また、ESXi ホストに設定したタイムアウトよりも特定 VM のタイムアウトを短くしたい場合は、VM 単位でもタイムアウトを設定できます。\n# 現在の vSphere vMotion Notifications の有効状態を確認 Get-VMVmOpNotification -Name \u0026lt;VM_NAME\u0026gt; # vSphere vMotion Notifications の有効化 (vmOpNotificationToAppEnabled = true) Set-VMVmOpNotification -Name \u0026lt;VM_NAME\u0026gt; -Enabled \u0026#34;true\u0026#34; # vSphere vMotion Notifications の無効化 (vmOpNotificationToAppEnabled = false) Set-VMVmOpNotification -Name \u0026lt;VM_NAME\u0026gt; -Enabled \u0026#34;false\u0026#34; # タイムアウト値の設定 (vmOpNotificationTimeout = \u0026lt;VALUE\u0026gt;) Set-VMVmOpNotification -Name \u0026lt;VM_NAME\u0026gt; -Timeout \u0026lt;VALUE\u0026gt; ゲスト OS での通知の操作 ESXi と VM に設定が入れば、ゲスト OS で通知を受け取る準備は完了です。ここからは、ゲスト OS 側での実施が必要な操作です。これには、VMware Tools（または Open VM Tools）に含まれる vmtoolsd コマンドを利用します。\n以下、 Linux の Bash での操作例です。VMware Tools（または Open VM Tools）が入っていれば vmtoolsd にもデフォルトでパスが通っているようなので、愚直にコマンドを叩いていけば動作します。\nWindows では、vmtoolsd はデフォルトで C:\\Program Files\\VMware\\VMware Tools\\vmtoolsd.exe として配置されています。操作方法は Linux のそれとまったく一緒ですが、コマンドプロンプトにしても PowerShell にしてもクォート周辺のエスケープがたいへんしんどいので、--cmd ではなく --cmdfile を利用した操作（後述）をおすすめします。\nアプリケーションの新規登録と登録解除 ゲスト OS 側で通知を受け取るには、前提としてアプリケーションの登録が必要です。任意の名前を付けて register コマンドを実行します。以下では例として demo としています。notificationTypes は正直よくわかりませんが、とにかく sla-miss とすれば動作しました。\n$ vmtoolsd --cmd \u0026#39;vm-operation-notification.register {\u0026#34;appName\u0026#34;: \u0026#34;demo\u0026#34;, \u0026#34;notificationTypes\u0026#34;: [\u0026#34;sla-miss\u0026#34;]}\u0026#39; {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true, \u0026#34;guaranteed\u0026#34;: true, \u0026#34;uniqueToken\u0026#34;: \u0026#34;525b5364-6caf-24a0-562c-87955647baa4\u0026#34;, \u0026#34;notificationTimeoutInSec\u0026#34;: 120 } 登録できると、トークン（uniqueToken）が発行されます。これは後続の操作で必要ですが、登録時しか表示されない ため、何らかの形で保持しておくと安心です。\n登録されているアプリケーションの情報は、list コマンドで確認できます。登録できるアプリケーションは VM ごとにひとつのみ です。\n$ vmtoolsd --cmd \u0026#39;vm-operation-notification.list\u0026#39; {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true, \u0026#34;info\u0026#34;: [ {\u0026#34;appName\u0026#34;: \u0026#34;demo\u0026#34;, \u0026#34;notificationTypes\u0026#34;: [\u0026#34;sla-miss\u0026#34;]}] } 再登録したくなった場合などは、トークンを渡して既存のアプリケーションの登録を unregister コマンドで解除します。\n$ vmtoolsd --cmd \u0026#39;vm-operation-notification.unregister {\u0026#34;uniqueToken\u0026#34;: \u0026#34;525b5364-6caf-24a0-562c-87955647baa4\u0026#34;}\u0026#39; {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true } 通知の有無の確認 アプリケーションを登録すると、発行されたトークンを使って、check-for-event コマンドで通知の有無を確認できるようになります。\nvmtoolsd --cmd \u0026#39;vm-operation-notification.check-for-event {\u0026#34;uniqueToken\u0026#34;: \u0026#34;525b5364-6caf-24a0-562c-87955647baa4\u0026#34;}\u0026#39; この実行結果には、通知の有無によって以下のようなパタンがあります。なお、check-for-event コマンドの実行間隔次第では複数の通知が滞留する状況も想定されますが、その場合でもコマンド一回ごとに 古い通知から一つずつ順に取得される ようです。\n# 通知が何もないとき {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true } # vMotion の開始の通知 {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true, \u0026#34;eventType\u0026#34;: \u0026#34;start\u0026#34;, \u0026#34;opType\u0026#34;: \u0026#34;host-migration\u0026#34;, \u0026#34;eventGenTimeInSec\u0026#34;: 1666730185, \u0026#34;notificationTimeoutInSec\u0026#34;: 120, \u0026#34;destNotificationTimeoutInSec\u0026#34;: 120, \u0026#34;notificationTypes\u0026#34;: [\u0026#34;sla-miss\u0026#34;], \u0026#34;operationId\u0026#34;: 6279072692702276663 } # vMotion の完了の通知 {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true, \u0026#34;eventType\u0026#34;: \u0026#34;end\u0026#34;, \u0026#34;opType\u0026#34;: \u0026#34;host-migration\u0026#34;, \u0026#34;opStatus\u0026#34;: \u0026#34;success\u0026#34;, \u0026#34;eventGenTimeInSec\u0026#34;: 1666730200, \u0026#34;notificationTypes\u0026#34;: [\u0026#34;sla-miss\u0026#34;], \u0026#34;operationId\u0026#34;: 6279072692702276663 } # タイムアウト値の変更の通知 {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true, \u0026#34;eventType\u0026#34;: \u0026#34;timeout-change\u0026#34;, \u0026#34;eventGenTimeInSec\u0026#34;: 1666736715, \u0026#34;notificationTimeoutInSec\u0026#34;: 120, \u0026#34;newNotificationTimeoutInSec\u0026#34;: 60, \u0026#34;notificationTypes\u0026#34;: [\u0026#34;sla-miss\u0026#34;], \u0026#34;operationId\u0026#34;: 1666736715415723 } 通知への応答 \u0026quot;eventType\u0026quot;: \u0026quot;start\u0026quot; の通知を受け取ってから vMotion の事前準備ができたら、準備の完了を ESXi 側に ack-event コマンドで応答する必要があります。\nこのコマンドには、トークンだけでなく、\u0026quot;eventType\u0026quot;: \u0026quot;start\u0026quot; の通知に含まれていた operationId も含める必要があります。\n$ vmtoolsd --cmd \u0026#39;vm-operation-notification.ack-event {\u0026#34;operationId\u0026#34;: 6279072692702276663, \u0026#34;uniqueToken\u0026#34;: \u0026#34;525b5364-6caf-24a0-562c-87955647baa4\u0026#34;}\u0026#39; {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true, \u0026#34;operationId\u0026#34;: 6279072692702276663, \u0026#34;ackStatus\u0026#34;: \u0026#34;ack_received\u0026#34; } 補足： ゲスト OS 側で必要な実装 まとめると、ゲスト OS 側には以下のような仕組みが必要です。\ncheck-for-event コマンドを一定の間隔で定期的に実行する \u0026quot;eventType\u0026quot;: \u0026quot;start\u0026quot; の通知があれば vMotion の事前準備を行う 事前準備が完了したら ack-event コマンドを実行する \u0026quot;eventType\u0026quot;: \u0026quot;end\u0026quot; の通知があれば vMotion の事後処理を行う 補足： Windows での通知の操作 Windows で PowerShell やコマンドプロンプトを使って vmtoolsd を操作したい場合、前述のような --cmd ではなく、--cmdfile の利用がおすすめです。--cmd に渡していた文字列をそのままテキストファイルに保存し、そのテキストファイルのパスを --cmdfile で渡すだけです。\n例えば、以下のコマンドを実行したいとき、\nvmtoolsd --cmd \u0026#39;vm-operation-notification.unregister {\u0026#34;uniqueToken\u0026#34;: \u0026#34;525b5364-6caf-24a0-562c-87955647baa4\u0026#34;}\u0026#39; 以下の内容のテキストファイルを作成し、\nvm-operation-notification.unregister {\u0026#34;uniqueToken\u0026#34;: \u0026#34;525b5364-6caf-24a0-562c-87955647baa4\u0026#34;} このファイルのパスを --cmdfile で指定します。\n\u0026gt; \u0026#34;C:\\Program Files\\VMware\\VMware Tools\\vmtoolsd.exe\u0026#34; --cmdfile \u0026#34;C:\\Path\\To\\CommandFile.txt\u0026#34; なお、Windows 上の Bash（Git Bash や WSL2 環境など）であれば、前述の Linux での操作例と同様に --cmd でも素直に操作できます。\n実際の操作と動き ここから、実際の動きを確認します。\n例として、VM vmotion-lnx01 で vSphere vMotion Notifications を有効化して、タイムアウトを 120 秒にしておきます。ESXi のタイムアウトは 300 秒にしています。\nPS\u0026gt; Set-VMVmOpNotification -Name vmotion-lnx01 -Enabled \u0026#34;true\u0026#34; -Timeout 120 Name vmOpNotificationToAppEnabled vmOpNotificationTimeout ---- ---------------------------- ----------------------- vmotion-lnx01 True 120 PS\u0026gt; Set-VMHostVmOpNotification -Name kuro-esxi01.krkb.lab -Timeout 300 Name VmOpNotificationToApp.Timeout ---- ----------------------------- kuro-esxi01.krkb.lab 300 PS\u0026gt; Set-VMHostVmOpNotification -Name kuro-esxi02.krkb.lab -Timeout 300 Name VmOpNotificationToApp.Timeout ---- ----------------------------- kuro-esxi02.krkb.lab 300 手動での動作確認 まずは手動で動きを追いかけます。最初にゲスト OS からアプリケーションを登録します。\n$ vmtoolsd --cmd \u0026#39;vm-operation-notification.register {\u0026#34;appName\u0026#34;: \u0026#34;demo\u0026#34;, \u0026#34;notificationTypes\u0026#34;: [\u0026#34;sla-miss\u0026#34;]}\u0026#39; {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true, \u0026#34;guaranteed\u0026#34;: true, \u0026#34;uniqueToken\u0026#34;: \u0026#34;52377485-336c-62bb-9af8-11e1628bfe66\u0026#34;, \u0026#34;notificationTimeoutInSec\u0026#34;: 120 } ゲスト OS で通知を毎秒モニタリングする while ループを回しておきます。\n$ while true; do vmtoolsd --cmd \u0026#39;vm-operation-notification.check-for-event {\u0026#34;uniqueToken\u0026#34;: \u0026#34;52377485-336c-62bb-9af8-11e1628bfe66\u0026#34;}\u0026#39;; sleep 1; done {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true } {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true } {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true } ... この状態で vCenter Server から vMotion をトリガすると、ゲスト OS に通知が届くことが確認できます。\n... {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true } {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true } {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true, \u0026#34;eventType\u0026#34;: \u0026#34;start\u0026#34;, \u0026#34;opType\u0026#34;: \u0026#34;host-migration\u0026#34;, \u0026#34;eventGenTimeInSec\u0026#34;: 1668173173, \u0026#34;notificationTimeoutInSec\u0026#34;: 120, \u0026#34;destNotificationTimeoutInSec\u0026#34;: 120, \u0026#34;notificationTypes\u0026#34;: [\u0026#34;sla-miss\u0026#34;], \u0026#34;operationId\u0026#34;: 6279072931629801319 } {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true } {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true } ... vMotion のタスクは Migrating Virtual Machine active state の状態で進行が一時停止しています。通知への応答を待っている状態です。\nタイムアウトは、VM では 120 秒、ESXi では 300 秒に設定していました。両方に設定がある場合は小さい値が採用されるため、今回の実際のタイムアウトは 120 秒です（通知の notificationTimeoutInSec でも確認できます）。ここで、120 秒以内にゲスト OS から ack-event を実行します。\n$ vmtoolsd --cmd \u0026#39;vm-operation-notification.ack-event {\u0026#34;operationId\u0026#34;: 6279072931629801319, \u0026#34;uniqueToken\u0026#34;: \u0026#34;52377485-336c-62bb-9af8-11e1628bfe66\u0026#34;}\u0026#39; {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true, \u0026#34;operationId\u0026#34;: 6279072931629801319, \u0026#34;ackStatus\u0026#34;: \u0026#34;ack_received\u0026#34; } 画面は割愛しますが、一時停止していた vMotion のタスクがこの段階で動作を再開することが確認できます。その後 vMotion が完了すると、ゲスト OS にも通知されます。\n... {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true } {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true } {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true } {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true, \u0026#34;eventType\u0026#34;: \u0026#34;end\u0026#34;, \u0026#34;opType\u0026#34;: \u0026#34;host-migration\u0026#34;, \u0026#34;opStatus\u0026#34;: \u0026#34;success\u0026#34;, \u0026#34;eventGenTimeInSec\u0026#34;: 1668173300, \u0026#34;notificationTypes\u0026#34;: [\u0026#34;sla-miss\u0026#34;], \u0026#34;operationId\u0026#34;: 6279072931629801319 } {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true } {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true } {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true } ... 基本的な動きが確認できました。\n自動処理スクリプトでの動作確認 実運用では、uniqueToken や operationId の扱いを含め、通知の確認や事前準備、事後処理はすべて自動で行われるべきです。今回は、イメージアップしやすいように次のようなスクリプトを作りました。Linux 向けです（検証用であり本番利用は想定していません）。\n指定した名前（--name）でアプリケーションを登録する 指定した間隔（--interval）で通知を確認する start の通知を受け取ったときに、指定したコマンド（--quiesce）を実行し、完了後に応答を返す end の通知を受け取ったときに、指定したコマンド（--unquiesce）を実行する スクリプトの終了時にアプリケーションを登録解除する 詳細な動作をログに保存する 実物は GitHub のリポジトリ に配置してあります。\n必要な引数と共にスクリプトを実行すると、アプリケーションを登録し、通知の監視を開始します。\n$ python3 handle_notifications.py --name demo --interval 10 \\ \u0026gt; --quiesce \u0026#34;${PWD}/demo.sh -m quiesce\u0026#34; \\ \u0026gt; --unquiesce \u0026#34;${PWD}/demo.sh -m unquiesce\u0026#34; 2022-10-29 07:04:03,025 [INFO] register application: demo 2022-10-29 07:04:03,035 [INFO] application has been registered with issued token: 52964f1a-6c7e-b373-b588-545b39581e1a 2022-10-29 07:04:03,035 [INFO] start monitoring at 10 second intervals (ctrl + c or kill to interrupt) 2022-10-29 07:04:03,035 [INFO] check if application is notified ... この状態で vMotion をトリガすると、start の通知を受けて、引数 --quiesce で指定したコマンド ${PWD}/demo.sh -m quiesce が実行されます。コマンドが完了すると通知に対する応答を返し、通知の監視を再開します。\n... 2022-10-29 07:04:33,091 [INFO] check if application is notified 2022-10-29 07:04:33,111 [INFO] application is notified that vmotion has been requested and should be quiesced in 120 seconds. invoke command to quiesce 2022-10-29 07:04:33,116 [INFO] \u0026gt; Application HOGE is requested to be quiesced. 2022-10-29 07:04:33,116 [INFO] \u0026gt; Quiescing ... leaving target pool for load balancing ... ... 2022-10-29 07:04:39,140 [INFO] \u0026gt; Quiescing ... stopping service BAZ 2022-10-29 07:04:40,144 [INFO] \u0026gt; Application HOGE has been quiesced and ready for vMotion. 2022-10-29 07:04:40,144 [INFO] command to quiesce has been completed with return code: 0 2022-10-29 07:04:40,144 [INFO] acknowledge notification 2022-10-29 07:04:40,156 [INFO] got response: ack_received 2022-10-29 07:04:50,161 [INFO] check if application is notified ... vMotion が完了して end の通知を受け取ると、今度は引数 --unquiesce で指定されたコマンド ${PWD}/demo.sh -m unquiesce を実行します。\n... 2022-10-29 07:04:50,172 [INFO] application is notified that vmotion has been completed and should be unquiesced. invoke command to unquiesce 2022-10-29 07:04:50,175 [INFO] \u0026gt; Application HOGE is requested to be unquiesced. 2022-10-29 07:04:50,175 [INFO] \u0026gt; UnQuiescing ... starting service BAZ ... 2022-10-29 07:04:56,190 [INFO] \u0026gt; UnQuiescing ... joining target pool for load balancing ... 2022-10-29 07:04:57,193 [INFO] \u0026gt; Application HOGE has been unquiesced and is in production. 2022-10-29 07:04:57,194 [INFO] command to unquiesce has been completed with return code: 0 2022-10-29 07:05:07,203 [INFO] check if application is notified ... kill や Ctrl + C でスクリプトを終了すると、アプリケーションの登録を解除してから終了します。\n... 2022-10-29 07:10:56,154 [INFO] interrupted 2022-10-29 07:10:56,154 [INFO] unregister application: demo 2022-10-29 07:10:56,164 [INFO] application has been unregistered ログファイルには実際のトークンやコマンドなど詳細な情報が保持されます。\n$ cat handle_notifications.log 2022-10-29 07:04:03,024 [DEBUG] monitor: application name: demo 2022-10-29 07:04:03,025 [DEBUG] monitor: command to quiesce: [\u0026#39;/.../examples/demo.sh -m quiesce\u0026#39;] 2022-10-29 07:04:03,025 [DEBUG] monitor: command to unquiesce: [\u0026#39;/.../examples/demo.sh -m unquiesce\u0026#39;] 2022-10-29 07:04:03,025 [DEBUG] monitor: monitoring interval: 10 second(s) 2022-10-29 07:04:03,025 [INFO] monitor: register application: demo 2022-10-29 07:04:03,025 [DEBUG] vmtoolsd: invoke command: [\u0026#39;vmtoolsd\u0026#39;, \u0026#39;--cmd\u0026#39;, \u0026#39;vm-operation-notification.register {\u0026#34;appName\u0026#34;: \u0026#34;demo\u0026#34;, \u0026#34;notificationTypes\u0026#34;: [\u0026#34;sla-miss\u0026#34;]}\u0026#39;] 2022-10-29 07:04:03,034 [DEBUG] vmtoolsd: rc: 0 2022-10-29 07:04:03,034 [DEBUG] vmtoolsd: stdout: {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true, \u0026#34;guaranteed\u0026#34;: true, \u0026#34;uniqueToken\u0026#34;: \u0026#34;52964f1a-6c7e-b373-b588-545b39581e1a\u0026#34;, \u0026#34;notificationTimeoutInSec\u0026#34;: 120 } 2022-10-29 07:04:03,035 [INFO] monitor: application has been registered with issued token: 52964f1a-6c7e-b373-b588-545b39581e1a 2022-10-29 07:04:03,035 [INFO] monitor: start monitoring at 10 second intervals (ctrl + c or kill to interrupt) 2022-10-29 07:04:03,035 [INFO] monitor: check if application is notified 2022-10-29 07:04:03,035 [DEBUG] vmtoolsd: invoke command: [\u0026#39;vmtoolsd\u0026#39;, \u0026#39;--cmd\u0026#39;, \u0026#39;vm-operation-notification.check-for-event {\u0026#34;uniqueToken\u0026#34;: \u0026#34;52964f1a-6c7e-b373-b588-545b39581e1a\u0026#34;}\u0026#39;] 2022-10-29 07:04:03,043 [DEBUG] vmtoolsd: rc: 0 2022-10-29 07:04:03,043 [DEBUG] vmtoolsd: stdout: {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true } ... 2022-10-29 07:04:33,091 [INFO] monitor: check if application is notified 2022-10-29 07:04:33,092 [DEBUG] vmtoolsd: invoke command: [\u0026#39;vmtoolsd\u0026#39;, \u0026#39;--cmd\u0026#39;, \u0026#39;vm-operation-notification.check-for-event {\u0026#34;uniqueToken\u0026#34;: \u0026#34;52964f1a-6c7e-b373-b588-545b39581e1a\u0026#34;}\u0026#39;] 2022-10-29 07:04:33,111 [DEBUG] vmtoolsd: rc: 0 2022-10-29 07:04:33,111 [DEBUG] vmtoolsd: stdout: {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true, \u0026#34;eventType\u0026#34;: \u0026#34;start\u0026#34;, \u0026#34;opType\u0026#34;: \u0026#34;host-migration\u0026#34;, \u0026#34;eventGenTimeInSec\u0026#34;: 1666994660, \u0026#34;notificationTimeoutInSec\u0026#34;: 120, \u0026#34;destNotificationTimeoutInSec\u0026#34;: 120, \u0026#34;notificationTypes\u0026#34;: [\u0026#34;sla-miss\u0026#34;], \u0026#34;operationId\u0026#34;: 6279072957175440971 } 2022-10-29 07:04:33,111 [INFO] monitor: application is notified that vmotion has been requested and should be quiesced in 120 seconds. invoke command to quiesce 2022-10-29 07:04:33,111 [DEBUG] invoke_command: command for quiesce/unquiesce: [\u0026#39;/.../examples/demo.sh -m quiesce\u0026#39;] 2022-10-29 07:04:33,116 [INFO] invoke_command: \u0026gt; Application HOGE is requested to be quiesced. 2022-10-29 07:04:33,116 [INFO] invoke_command: \u0026gt; Quiescing ... leaving target pool for load balancing ... ... 2022-10-29 07:04:39,140 [INFO] invoke_command: \u0026gt; Quiescing ... stopping service BAZ 2022-10-29 07:04:40,144 [INFO] invoke_command: \u0026gt; Application HOGE has been quiesced and ready for vMotion. 2022-10-29 07:04:40,144 [DEBUG] invoke_command: return code: 0 2022-10-29 07:04:40,144 [INFO] monitor: command to quiesce has been completed with return code: 0 2022-10-29 07:04:40,144 [INFO] monitor: acknowledge notification 2022-10-29 07:04:40,144 [DEBUG] vmtoolsd: invoke command: [\u0026#39;vmtoolsd\u0026#39;, \u0026#39;--cmd\u0026#39;, \u0026#39;vm-operation-notification.ack-event {\u0026#34;uniqueToken\u0026#34;: \u0026#34;52964f1a-6c7e-b373-b588-545b39581e1a\u0026#34;, \u0026#34;operationId\u0026#34;: 6279072957175440971}\u0026#39;] 2022-10-29 07:04:40,156 [DEBUG] vmtoolsd: rc: 0 2022-10-29 07:04:40,156 [DEBUG] vmtoolsd: stdout: {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true, \u0026#34;operationId\u0026#34;: 6279072957175440971, \u0026#34;ackStatus\u0026#34;: \u0026#34;ack_received\u0026#34; } 2022-10-29 07:04:40,156 [INFO] monitor: got response: ack_received ... 2022-10-29 07:10:56,154 [INFO] monitor: interrupted 2022-10-29 07:10:56,154 [INFO] monitor: unregister application: demo 2022-10-29 07:10:56,155 [DEBUG] vmtoolsd: invoke command: [\u0026#39;vmtoolsd\u0026#39;, \u0026#39;--cmd\u0026#39;, \u0026#39;vm-operation-notification.unregister {\u0026#34;uniqueToken\u0026#34;: \u0026#34;52964f1a-6c7e-b373-b588-545b39581e1a\u0026#34;}\u0026#39;] 2022-10-29 07:10:56,164 [DEBUG] vmtoolsd: rc: 0 2022-10-29 07:10:56,164 [DEBUG] vmtoolsd: stdout: {\u0026#34;version\u0026#34;:\u0026#34;1.0.0\u0026#34;, \u0026#34;result\u0026#34;: true } 2022-10-29 07:10:56,164 [INFO] monitor: application has been unregistered 人間の介入を必要としない実装も、どうにかできそうなことが確認できました。\nまとめ vSphere 8 の新機能のひとつ、vSphere vMotion Notifications の動きを確認しました。\nとはいえ、個人的には vMotion に耐えられないアプリケーションを扱う機会が日常ではあまりないのでそもそも出番がなさそうではありますが、動きとしてはおもしろいですね。\n","date":"2022-11-11T14:48:37Z","image":"/archives/4673/img/image-337.png","permalink":"/archives/4673/","title":"vSphere vMotion Notifications を試す"},{"content":"はじめに vSphere 8 が公開された ので、おうちラボをアップグレードしました。本エントリはその記録です。\nなお、vSphere 8 から製品のリリースモデルに変更があり、従来のように いきなり GA（General Availability）ではなく、今後は IA（Initial Availability）と GA の 2 段階方式 になるようです。ということで、今回は厳密には IA リリースへのアップグレード です。\n詳細は次の公式ブログのエントリで解説されていますが、ざっくりいえば、従来の GA 相当のモノをまず IA の扱いでリリース し、1 ～ 2 ヶ月ほど経って IA の採用が充分に広まったら GA 扱いに変更する ようです。GA 前の公開という意味では IA がいわゆるパブリックベータのような扱いとも思えてしまいますが、そうではなく、IA でも品質は従来の GA と何ら変わらないということのようでした。\nOur intent going forward is that all major and update vSphere releases will be delivered first with an IA designation. An IA release is a production-quality release that meets all GA quality gates and is fully partner certified. IA releases will be available during the IA phase to all customers for production deployments.\nWe will follow up once we determine each release has achieved sufficiently wide adoption and announce the transition of the release to a GA designation. We expect this to typically happen after 4-6 weeks from IA. New Release Model for vSphere 8 - VMware vSphere Blog\r率先して新しいモノを使いたいヒト向けが IA で、ある程度ワールドワイドで実績ができてから採用したいヒト向けが GA って感じでしょうか。たぶん。\nとはいえ、IA から GA まで最大で 2 ヶ月ちかく空くことになりそうなので、IA のビルドが本当に完全にそのまま GA になるのか、あるいはなんだかんだで GA にあわせてパッチリリースなど新しいビルドが出てくるのか、断定はしづらそうです。いつものビルド番号の一覧の KB（vCenter Server、ESXi）では 8.0 IA と書かれているので、この行が GA に書き換わるのか、あるいは行が増えるのか、要注視ということで。\n作業の流れ 今回の対象の環境は、おうちラボのうち、vSphere 7 環境を構成している第 8 世代の NUC（NUC8i5BEK）2 台です。ハードウェア面もソフトウェア面も、どちらも何も凝った構成にはしておらず、vSAN も使っていないほぼ素のままのミニマム状態です。これまで通り、次の流れで進めます。\nvCenter Server のアップグレード ESXi のアップグレード 分散仮想スイッチ（vSphere Distributed Switch）のアップグレード 実際の作業内容は、当然ながら対象の構成に強く依存します。考慮事項も多数あるため、リリースノート、製品ドキュメントのアップグレードの項目、相互運用性マトリクス（Interoperability Matrices）、互換性ガイド（Compatibility Guide）あたりは確認すると安心です。いずれも次の Activity Path から追いかけられるため、これを順に進めながら確認するとよいでしょう。\nvSphere 8 Upgrade Activity Path 個別のドキュメントは次の通りです。\nVMware vSphere 8.0 Release Notes VMware vSphere Documentation About vCenter Server Upgrade About VMware ESXi Upgrade VMware Product Interoperability Matrix VMware Compatibility Guide vCenter Server の 7.0 から 8.0 へのアップグレード ほぼ vCenter Server を 6.7 から 7.0 にアップグレードしたとき と同じ流れです。ステージ 1 で新しい vCSA が（仮の IP アドレスで）デプロイされ、ステージ 2 でデータの移行、IP アドレスの修正と旧 vCSA の停止が行われます。\n今回も GUI インストーラを使っています。Upgrade を選択して進みます。\nステージ 1： vCenter Server のデプロイ 愚直にウィザードに従っていきます。特に難しいところはないので、抜粋しつつ画像だけ並べます。\nまずは移行元の環境情報を指定します。\n続く画面で移行先の環境情報を指定したら、新しい vCSA の VM 名を指定します。今回の構成では、新 vCSA が旧 vCSA と同じインベントリに並ぶことになるので、名前の競合を避けるために旧 vCSA とは別のものを指定します。\nvCSA のサイズで選択できる最小サイズは、移行元の vCSA の CPU とメモリ、ディスクのサイズ（使用量ではなく割当量）から判断されるようです。今回の環境では本当は Tiny で充分ですが、元の vCSA のディスクが少し大きかったので Tiny が選べず、Small にしています。\nデータストアとネットワークを指定します。\n構成をレビュして、問題なければデプロイを開始させます。\n完了を待ちます。だいたい 10 分くらいでした。\nステージ 2： 移行元 vCenter Server のアップグレード 続けてそのままステージ 2 です。こちらも愚直にウィザードに従うのみです。\n移行元の vCenter Server の情報を渡して、移行する対象のデータを選択します。過去の履歴は破棄して構わないので、今回は設定とインベントリだけ移行させます。\nCEIP へ参加するといくつかの機能が有効化されるので、素直に参加します。\n構成をレビュし、問題なければ移行を開始させます。\n新旧 vCSA 間でデータの移行が行われ、旧 vCSA が停止されます。その後、旧 vCSA の IP アドレスなどのネットワーク設定が新 vCSA に反映されます。\n完了です。データ量に大きく左右されますが、今回の環境ではだいたい 20 分くらいでした。\n完成 サマリタブがタイルの並んだ新しいダッシュボードになっています。日本語表記だと少し表示崩れが気になりました（容量と使用量 のタイル）が、英語表記にしたらすっきりしました。日本語はワードラップが難しいですね。\nESXi の 7.0 から 8.0 へのアップグレード ESXi のアップグレードには、今回は vSphere Lifecycle Manager（vLCM）の イメージ管理 を利用します。なお、vSphere 8 からは旧 VUM で使われていた ベースライン管理は非推奨 になり、次のメジャバージョンからは廃止されるようです。\nvLCM を使った諸々は過去に本ブログでもいくつか取り上げていますが、vLCM やイメージ管理の概念自体が vSphere 7 からの登場だったので、メジャバージョンアップに vLCM を使うのは今回が初めてです。\nvSphere Lifecycle Manager (vLCM) で ESXi を 6.7 から 7.0 にアップグレードして管理する NUC 8 の ESXi を 7.0b から 7.0 update 1 にアップグレードする 新しいドライバで NUC 8 のオンボード NIC を ESXi 7.0U1 で使えるようにする とはいえ、特に難しいこともなく、ホストクラスタに対してイメージを定義して修正するだけの簡単な作業で完了します。\nイメージの編集 ホストクラスタの アップデート \u0026gt; イメージ から適用するイメージを編集すると、ベースイメージに ESXi 8.0 が選択肢にある（表記が GA になっていますが IA の間違い……？）ので、それを選択します。\n今回はハードウェアが NUC 8 なので、Community Network Devices Driver（e1000-community）の最新版もアップロードして追加しました（追加方法は 以前のエントリ で紹介しています）。\nホストの修正 イメージができたら コンプライアンスの確認 をして、ホストごと、あるいはクラスタ全体で 修正 を行えば完了です。\nなお、今回は使いませんでしたが、vSphere 8 から、修正作業の時間を短くするために事前にイメージを ESXi に送る ステージ と、あらかじめメンテナンスモードにしておくことで複数のホストを同時に修正する 並列修正（Parallel remidiation）ができるようになっています。\n完成 だいたい 10 分くらいで完了しました。\nvCenter Server がある環境だとあまり触りませんが、ESXi 側の Host Client にもアップデートが入っています。\n詳細はリリースノートに記載 がありますが、最新の仮想ハードウェアバージョンへの対応など順当な更新だけでなく、UI のテーマの変更のサポート、ESXi への SSH コンソール接続機能（Chrome への拡張機能のインストールが必要です）などが追加されています。\n補足： vCSA の内部用 DNS サーバ化 これまで、おうちラボでは、vCSA 7.0 を内部用 DNS サーバとして使っていました。この構成を今後も維持したいので、アップグレードと同時に vCSA 8.0 に対しても同様の DNS サーバ化を行いました。\n今回、ステージ 1 の完了後、ステージ 2 を開始する前に手動で構成作業を行っています。ステージ 1 までは旧 vCSA を DNS サーバとして使えますが、ステージ 2 ではその過程で旧 vCSA がシャットダウンされ名前解決ができなくなってしまうためです。ステージ 2 も最低限 /etc/hosts さえいじってあれば DNS サーバを使わずとも完遂できますが、ついでに全部やってしまっています。\nただし、ステージ 2 の最中に /etc/dnsmasq.conf がロールバックされたため、Dnsmasq の構成だけはアップグレードの完了後にも再度実行しました。\n作業の内容自体は、vCSA 7.0 向けのもの とまったく一緒です。\nSSH の有効化 SSH で接続して作業したいので、vCSA のコンソール（DCUI）を開いて Troubleshooting Mode Options から Enable SSH で SSH 接続を有効化します。\n/etc/hosts の修正 旧 vCSA の /etc/hosts の必要なエントリを、vCSA 8.0 の /etc/hosts に移植します。\n# vi /etc/hosts # cat /etc/hosts ... 192.168.0.200 kuro-qnap01.krkb.lab kuro-qnap01 192.168.0.201 kuro-vcs01.krkb.lab kuro-vcs01 192.168.0.202 kuro-esxi01.krkb.lab kuro-esxi01 192.168.0.203 kuro-esxi02.krkb.lab kuro-esxi02 192.168.0.210 kuro-vro01.krkb.lab kuro-vro01 ... Dnsmasq の構成 /etc/dnsmasq.conf を修正して、Dnsmasq を再起動します。\n# sed -i \u0026#39;s/^listen-address/#listen-address/\u0026#39; /etc/dnsmasq.conf # sed -i \u0026#39;s/^no-hosts/#no-hosts\\nbogus-priv/\u0026#39; /etc/dnsmasq.conf # cat /etc/dnsmasq.conf #listen-address=127.0.0.1 bind-interfaces user=dnsmasq group=dnsmasq #no-hosts bogus-priv log-queries log-facility=/var/log/vmware/dnsmasq.log domain-needed dns-forward-max=150 cache-size=8192 neg-ttl=3600 # systemctl restart dnsmasq ファイアウォールの構成 vCSA 用のファイアウォールの設定ファイルを作成し、設定を再読み込みさせます。\n# cat \u0026lt;\u0026lt; EOF \u0026gt; /etc/vmware/appliance/firewall/vmware-dnsmasq { \u0026#34;firewall\u0026#34;: { \u0026#34;enable\u0026#34;: true, \u0026#34;rules\u0026#34;: [ { \u0026#34;direction\u0026#34;: \u0026#34;inbound\u0026#34;, \u0026#34;protocol\u0026#34;: \u0026#34;tcp\u0026#34;, \u0026#34;porttype\u0026#34;: \u0026#34;dst\u0026#34;, \u0026#34;port\u0026#34;: \u0026#34;53\u0026#34;, \u0026#34;portoffset\u0026#34;: 0 }, { \u0026#34;direction\u0026#34;: \u0026#34;inbound\u0026#34;, \u0026#34;protocol\u0026#34;: \u0026#34;udp\u0026#34;, \u0026#34;porttype\u0026#34;: \u0026#34;dst\u0026#34;, \u0026#34;port\u0026#34;: \u0026#34;53\u0026#34;, \u0026#34;portoffset\u0026#34;: 0 } ] } } EOF # /usr/lib/applmgmt/networking/bin/firewall-reload 動作確認 完了です。外部のホストから名前解決ができることを確認します。\n$ dig kuro-esxi01.krkb.lab @192.168.0.201 ... ;; ANSWER SECTION: kuro-esxi01.krkb.lab. 0 IN A 192.168.0.202 ... まとめ vCenter Server と ESXi をこれまでの 7.0 から最新の 8.0 にアップグレードしました。\nオンプレミスのちまっとした非 vSAN な環境だったこともあって、いつも通りな感覚であまり迷うこともなくサクサクと進められました。\nとはいえ、難易度や手順は構成次第で大きく変わりますし、時代にあわせて順当に古いハードウェアや古いゲスト OS のサポートが打ち切られてもいるので、きちんとした環境で採用するときは、きちんとリリースノートやドキュメントや各種資料など一次情報に目を通しましょうということで。\n","date":"2022-10-14T19:30:49Z","image":"/archives/4561/img/image-335.png","permalink":"/archives/4561/","title":"vSphere 8： vCenter Server と ESXi を 7.0 から 8.0 にアップグレードする"},{"content":"はじめに AWX の 21.7.0 から、Execution Node（実行ノード）がサポートされました。これにより、AWX が動作する Kubernetes クラスタの 外 の 独立したホスト に、ジョブの実処理を任せられる ようになります。\n構築方法のドキュメント も提供されており、これが親切なので愚直に従えば動くところまで比較的簡単に持っていけますが、本エントリでは、概要や構成方法、使い方を、実装面を少し紐解きつつ紹介します。\nExecution Node とは 概要 Kubernetes クラスタ上の AWX でジョブを実行すると、デフォルトの構成では、ジョブごとに Execution Environment（実行環境、EE）の Pod が作成されて処理されます。この場合、ジョブのターゲットノードには EE の Pod から接続できる必要があります。\n今回サポートされた Execution Node は、Kubernetes クラスタの 外 にある RHEL ファミリの 独立したホスト に、ジョブの実処理を任せられる機能です。\nExecution Node には、それを構成する過程で Receptor と Podman が導入され、Receptor により Kubernetes 上の AWX インスタンスとの間にメッシュネットワークが構成されます。AWX でジョブが実行されると、Receptor を介して Ansible Runner の Worker プロセスが起動し、Podman 上に EE のコンテナが作られて処理されます。\nメリット Execution Node を用意すると、ジョブのターゲットノードにはこの Execution Node から接続できればよくなります。つまり、Kubernetes クラスタからターゲットノードへの直接の接続性を確保する必要がなくなる ため、いわゆる 踏み台サーバに近い イメージでも使えることになります（ansible_ssh_common_args を使ってがんばらなくてもよくなる環境も多そうです）。ただし、ジョブを実行する EE のイメージのプルは Execution Node 上の Podman が直接行うため、コンテナレジストリへの到達性は必要です。\nまた、大規模な環境やジョブの同時実行数が多い環境では、ジョブ実行用のリソースを（Kubernetes クラスタへのノード追加ではない形で）簡単に増強できるようになります。リソースをジョブの実行で占有できるのもメリットです。\nAWX のこれまでの実装でも、技術的には Kubernetes のワーカノードを別に用意して EE の起動を Container Group でそのノードに限定すれば似たようなことは（やや強引に）実現できましたが、Kubernetes クラスタがマネージドサービスだったり、ノード間のネットワークの論理的・物理的制約が強かったりすると、そうした構成も難しいことがありました。\nこの点でも、今回の Execution Node のサポートで、複雑なネットワーク環境でもシンプルで柔軟な構成が取りやすくなったといえそうです。例えば、クラウド環境でリージョンをまたいだジョブの実行なども構成しやすくなるでしょう。\nなお、AWX のインスタンスと Execution Node の間の通信は TLS の証明書による認証と暗号化が行われます。この証明書の発行には、デフォルトでは自動生成された自己署名 CA 証明書が使われますが、Kubernetes 上に Secret リソースとして作成することで任意の CA 証明書に置き換えられる ようです。\n構成上の注意点（21.7.0 のみ） 本機能が実装された最初のリリースである 21.7.0 でのみ、Execution Node 用に生成される 証明書の有効期限が 10 日間 で固定されている問題があります。\nAWX にも Receptor にも証明書の自動更新の機構は（少なくとも現時点では）存在しないため、証明書の有効期限が切れると Receptor としての通信ができなくなります。したがって、21.7.0 で本機能を本格的に使う場合、高い頻度で証明書を手動で更新し続ける運用が必要です。証明書の更新手順は 後述の補足 で紹介しています。\n本件はすでに Issue で報告済みで、PR もマージ済みのため、次のリリースでは修正されます。急ぎでなければ、本格的な活用は次のリリースを待つほうがよいでしょう（追記： この問題は 21.8.0 で修正されました）。\n参考： Ansible Automation Platform での実装 商用版の Ansible Automation Platform（AAP）では、2.1 から Automation Mesh として近い機能がリリースされています。\nRed Hat Ansible Automation Platform automation mesh guide Automation Mesh でも Execution Node は実行プレーンを構成する要素のひとつ として次のように定義されており、今回の AWX での Execution Node と同等のものといえそうです。\nThe execution plane consists of execution nodes that execute automation on behalf of the control plane and have no control functions. Hop nodes serve to communicate. Nodes in the execution plane only run user-space jobs, and may be geographically separated, with high latency, from the control plane. Red Hat Ansible Automation Platform automation mesh guide - 1.2.2. Execution plane\rExecution nodes - Execution nodes run jobs under ansible-runner with podman isolation. This node type is similar to isolated nodes. Red Hat Ansible Automation Platform automation mesh guide - 1.2.2. Execution plane\rなお、AAP の Automation Mesh では、Execution Node のほかに Hop Node も定義されています。これはジョブを他の Execution Node に中継する目的のホストですが、現時点の AWX では Execution Node ほど簡単には構成できないようです（余談ですが、語義としてはこちらのほうが『踏み台』には近いですね）。\nHop nodes - similar to a jump host, hop nodes will route traffic to other execution nodes. Hop nodes cannot execute automation. Red Hat Ansible Automation Platform automation mesh guide - 1.2.2. Execution plane\rExecution Node の構成 実際に Execution Node を構成して、AWX から利用します。基本的には AWX のドキュメント に従うのみですが、各所、もう少し詳しい説明を加えつつ紹介します。\n前提： 今回の構成と作業の流れ Execution Node を構成するには、次のモノが必要です。\nAWX（21.7.0 以降） Execution Node として構成する RHEL ファミリ（RHEL、CentOS、Fedora など）のホスト Execution Node として構成するホストに対して Ansible が実行できる環境 三点目は、Execution Node 用のホストを Execution Node として構成するためのインストール用プレイブックの実行に利用します。Execution Node 用のホストに Ansible として接続できさえすればよいので、構成にまったく無関係の適当な環境を使ってもよいですし、あるいは Execution Node 上で自分自身に対して実行する作戦もアリです。\nさて、今回は、シングルノードの K3s 上に構成した AWX（awx.example.com） に、Execution Node として CentOS Stream 8 のホスト（exec01.ansible.internal）を追加することを考えます。踏み台サーバのように使えることを確認するため、Execution Node をマルチホームで構成し、K3s ホストから直接の到達性がないネットワークにいるターゲット（kuro-dev01.ansible.private）をジョブで操作できることも確認します。\nまた、ジョブの実行に利用する EE は、デフォルトの公開イメージ（quay.io 上の awx-ee）のほか、AWX と同じ K3s クラスタ上に構成した プライベートコンテナレジストリ（registry.example.com）上のものも利用します。このコンテナレジストリは、自己署名証明書の HTTPS を使っており、認証も必須です。\n構成は次の流れで作業します。AWX 側の操作は API を直接叩いても行えますが、今回は Web GUI で作業しています。\nExecution Node 用ホストの準備 AWX での新規インスタンスの定義 Execution Node 用のインストールバンドルの取得 Execution Node 用のインストールバンドルの実行 AWX での新規インスタンスグループの定義 Execution Node 用ホストの準備 Execution Node として構成するホストを用意します。要件は次の通りです。\nRHEL ファミリの OS（RHEL、CentOS、Fedora など）であること 固定 IP アドレス、または AWX から DNS で名前解決できるホスト名を持つこと AWX から Receptor のポート（デフォルトで 27199/tcp）にアクセスできること 対応 OS が RHEL ファミリに限定されていますが、後述するインストールバンドルが利用している Ansible のロール ansible.receptor.setup が RHEL ファミリ向けに書かれていることも影響していそうです。\nなお、このホストの具体的な リソース要件 は AWX のドキュメントでは定められていませんが、AAP のドキュメント では Execution Node は 4 コアの CPU と 16 GB のメモリ が必要とされています。厳密にいえば別製品ですが、Execution Node の実装はほとんど同じと考えられるため、参考にはできそうです。このホストの CPU コア数やメモリ容量は、AWX 側でジョブの同時実行数などの計算に利用 されるため、利用する環境の規模に応じて Ansible Automation Controller（AAC） のドキュメント も併せて参考して調整するとよいでしょう。\n今回は、CentOS Stream 8 を用意しました。AAP のドキュメントを尊重して 4 vCPU、16 GB RAM で、最小インストール構成です。あらかじめ、Receptor で利用するポートをファイアウォールで許可 しておきます。ポート番号はデフォルトでは 27199/tcp です。\nsudo firewall-cmd --add-port=27199/tcp --permanent sudo firewall-cmd --reload また、後述の手順でインストール用のプレイブックをこのホストに対して実行することになるため、Ansible での接続に利用するユーザやそのための SSH の認証を構成しておきます。\nAWX での新規インスタンスの定義 Execution Node は、AWX 上では インスタンス の一種です。まずは、AWX の Administration \u0026gt; Instances \u0026gt; Add で、追加する Execution Node の情報を定義します。\n以降、AWX からはここで入力した Host Name に宛てて通信が発生し、証明書もこのホスト名に対して発行されます。したがって、ここでは AWX から名前解決ができて到達可能なホスト名 または 固定 IP アドレス を入力する必要があります。その他の修正は任意です。今回はすべてデフォルトにしています。\n入力後、Save を押下するとインスタンスが追加されます。\nこの段階で、AWX は新しいインスタンスのインストールの完了を待つ状態になり、awx-ee コンテナから Receptor での接続を試行してノードの状態を定期的に確認するようになります。現時点では当然ながら接続できません。\n$ kubectl -n awx logs -f deployment/awx -c awx-ee ... WARNING 2022/10/05 22:08:40 Backend connection failed (will retry): dial tcp 192.168.0.218:27199: connect: connection refused WARNING 2022/10/05 22:09:00 Backend connection failed (will retry): dial tcp 192.168.0.218:27199: connect: connection refused WARNING 2022/10/05 22:09:20 Backend connection failed (will retry): dial tcp 192.168.0.218:27199: connect: connection refused ... Execution Node 用のインストールバンドルの取得 前述の画面で、Install Bundle のダウンロードボタンをクリックして、そのインスタンス専用のインストールバンドル（\u0026lt;ホスト名\u0026gt;_install_bundle.tar.gz）をダウンロードします。\nこの中には、Receptor での通信に必要な CA 証明書やそのノード用の証明書のほか、それらを使ってホストを Execution Node として構成するために必要なプレイブックなどが含まれています。\nExecution Node 用のインストールバンドルの実行 インストールバンドルの実体は、Execution Node 用のホストをターゲットとする Ansible のプレイブックであり、手動で実行が必要 です。\nAnsible を実行できる適当な環境にインストールバンドルを配置して展開し、まずは同梱の requirements.yml を使ってプレイブックに必要なコレクションをインストールします。\nansible-galaxy collection install -r requirements.yml 続けて、インストールバンドルに含まれるインベントリファイル（inventory.yml）を編集します。ターゲットノードのホスト名には AWX 側で入力したホスト名があらかじめ含まれていますが、これ以外の箇所は修正が必要 です。プレイブックは become アリで実行される ため、権限昇格も踏まえて適宜修正します。\n--- all: hosts: remote-execution: ansible_host: exec01.ansible.internal ansible_user: \u0026lt;username\u0026gt; # user provided ansible_ssh_private_key_file: ~/.ssh/id_rsa 鍵認証向けの記述も含まれていますが、つながって実行できさえすればよいので、パスワード認証向けの記述に修正しても構いませんし、あるいはターゲットノード上で直接実行する場合は ansible_connection: local にするのもアリです。become のパスワードはインベントリファイルに書いておいてもよいですし、プレイブックを実行する段階で --ask-become-pass で与えてもよいでしょう。\n準備ができたら、プレイブックを実行して完了を待ちます。\n$ ansible-playbook -i inventory.yml install_receptor.yml ... PLAY RECAP ******************************************************************************************************* remote-execution : ok=32 changed=18 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 プレイブックが完了して AWX と正常に接続できるようになれば、数分で AWX 側のインスタンスの Status が Ready に変化します。これで、Execution Node がインスタンスとして追加されました。\nawx-ee コンテナのログからも、Receptor を介したネゴシエーションの様子が確認できますz\n$ kubectl -n awx logs -f deployment/awx -c awx-ee ... DEBUG 2022/10/06 20:12:16 Sending initial connection message INFO 2022/10/06 20:12:16 Connection established with exec01.ansible.internal DEBUG 2022/10/06 20:12:16 Stopping initial updates DEBUG 2022/10/06 20:12:16 Re-calculating routing table INFO 2022/10/06 20:12:16 Known Connections: DEBUG 2022/10/06 20:12:16 Sending routing update tNidLlEw. Connections: exec01.ansible.internal(1.00) INFO 2022/10/06 20:12:16 awx-667b7b7b54-bwhmv: exec01.ansible.internal(1.00) INFO 2022/10/06 20:12:16 exec01.ansible.internal: awx-667b7b7b54-bwhmv(1.00) INFO 2022/10/06 20:12:16 Routing Table: INFO 2022/10/06 20:12:16 exec01.ansible.internal via exec01.ansible.internal DEBUG 2022/10/06 20:12:16 Received routing update UMiwQwzW from exec01.ansible.internal via exec01.ansible.internal ... DEBUG 2022/10/06 20:13:11 Reloading ... DEBUG 2022/10/06 20:13:14 Stdout complete - closing channel for: quF9TI7P ... DEBUG 2022/10/06 20:13:14 closing connection from service gMPLKKYm to exec01.ansible.internal:control ... Ready にならず、Installed のまま変化しなかったり、Unavailable になってしまった場合、何らかの問題が発生していると考えられます。後述の トラブルシュート を参考に原因を特定し修正します。\nAWX での新規 Instance Group の定義 AWX でのジョブの実行は、インスタンス単位ではなく、Instance Group（または Container Group）に対して割り当てられます。したがって、特定のインスタンスでジョブを実行させる には、そのインスタンスを含んだ Instance Group が必要 です。\nまずは、AWX の Administration \u0026gt; Instance Groups \u0026gt; Add \u0026gt; Add instance group で任意の名称の Instance Group を定義します。\nその後、追加した Instance Group の Instances タブで Associate ボタンをクリックし、今回新しく登録した Execution Node のインスタンスを選択して Save ボタンをクリックします。\nこれで Instance Group に Execution Node がインスタンスとして追加され、ジョブの実行に利用できるようになりました。\n動作の確認 環境ができたので、ここからは Execution Node を使ったジョブの実行を実際にテストします。\nなお、Execution Node でジョブを実行するには、前述のとおりそのジョブに Instance Group を指定する必要があり、この指定は Organization 単位、Inventory 単位、もしくは Job Template 単位 で可能です。今回はいずれも次の図のように Job Template 単位で指定しています。\n基本的な動作の確認 まずは基本的な動作の確認として、デフォルトの EE（quay.io/ansible/awx-ee:latest）を使い、EE 内で完結するよう localhost をターゲットにしたジョブを実行させます。挙動が追いやすいよう、ここでは ansible.builtin.pause を含むプレイブック を Job Template として使っています。\nジョブを実行すると、実行中は Execution Node では Podman 上で EE のコンテナが動作していることが観察できます。このコンテナはユーザ awx（前述の手順で実行したインストール用プレイブックにより作成されています）の名前空間内で動作しているため、事前に su して観察します。\n$ sudo su - awx $ podman ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3e02ac3e9685 quay.io/ansible/awx-ee:latest ansible-playbook ... 39 seconds ago Up 40 seconds ago ansible_runner_8 ところで、Kubernetes クラスタ上の EE では ansible-runner コマンドが実行されていました が、上記出力からは、Execution Node 上の EE では ansible-playbook コマンドが実行されていることがわかります。\nこれは、Execution Node を用いたジョブの実行では、Ansible Runner の Worker プロセスは Executio Node 上で直接（非コンテナ環境で）動作 するためで、Podman はあくまでその Ansible Runner が Process Isolation の手段として利用 しているだけだからです。したがって、ansible-runner 用のファイル群も、Execution Node 上の /tmp に直接展開されています。\n# 関連プロセス群。 # Receptor が直接 Ansible Runner の Worker プロセスを起動していることがわかる。 # Ansible Runner は /tmp/awx_8_2g2vrzz7 を作業ディレクトリとして利用している。 $ ps -ef | grep -E \u0026#39;(receptor|ansible-runner)\u0026#39; | grep -v grep awx 16373 1 0 05:11 ? 00:00:08 /usr/bin/receptor -c /etc/receptor/receptor.conf awx 53942 16373 0 06:37 ? 00:00:00 /usr/bin/receptor --node id=worker --log-level info --command-runner command=ansible-runner params=worker --private-data-dir=/tmp/awx_8_2g2vrzz7 --delete unitdir=/tmp/receptor/exec01.ansible.internal/8CbcJ0G6 awx 53951 53942 1 06:37 ? 00:00:00 /usr/bin/python3.9 /usr/local/bin/ansible-runner worker --private-data-dir=/tmp/awx_8_2g2vrzz7 --delete # 関連ファイル群。 # Ansible Runner の処理に必要な実際のファイル群は、 # Execution Node 上の /tmp 下に直に展開されている。 $ ls -l /tmp/awx_8_2g2vrzz7 total 0 drwxr-xr-x. 3 awx awx 15 Oct 7 06:37 artifacts drwx------. 2 awx awx 6 Oct 6 21:37 cp drwxr-xr-x. 2 awx awx 54 Oct 7 06:37 env drwxr-xr-x. 2 awx awx 19 Oct 7 06:37 inventory drwxr-xr-x. 14 awx awx 228 Oct 7 06:37 project また、EE のイメージが Podman によりプルされていることから、コンテナレジストリへの接続性は Execution Node からも必要であることが確認できます。\n$ podman events --filter event=pull --since 1h ... 2022-10-07 06:37:10.577200393 +0900 JST image pull quay.io/ansible/awx-ee:latest ... $ podman images REPOSITORY TAG IMAGE ID CREATED SIZE quay.io/ansible/awx-ee latest 8982fdafc038 9 hours ago 1.96 GB 隔離ネットワーク上のターゲットへのジョブの実行 続けて、AWX が動作している K3s ホストからは到達性がないネットワークにあるターゲットノード（図中右上の kuro-dev01.ansible.private）に対するジョブの実行をテストします。図の通り、このターゲットには Execution Node からのみアクセス可能です。\nテストのため、AWX 上で Inventories と Credentials にターゲットノードへの接続に必要な情報を登録して、Job Template として facts を収集して ping 後に表示するだけの次の適当なプレイブックを追加しました。\n--- - hosts: all gather_facts: true tasks: - ansible.builtin.ping: - ansible.builtin.debug: var: ansible_facts まずは、Instance Group を 指定しないでジョブを実行 し、ターゲットノードに到達できずに失敗することを確認します。\n改めて Instance Group を 指定して再度ジョブを実行 すると、ジョブが成功するようになりました。正しく Execution Node 経由で接続できているようです。\n従来、Kubernetes クラスタの Pod から直接は接続できないネットワークにいるターゲットには、SSH を踏み台経由で到達させるために ansible_ssh_common_args を使ってがんばるなどするのが常套手段でしたが、Execution Node を使うと非常にシンプルに構成できるようになりそうです。\nプライベートコンテナレジストリの利用 最後に、プライベートコンテナレジストリ上の EE が使えることをテストします。準備として、Kubernetes クラスタ上に次のようなプライベートコンテナレジストリを構築し、EE のイメージをプッシュしました。\n自己署名証明書による HTTPS でホストされる 認証が必要である さらに AWX では、Verify SSL をオフにした Container Registry タイプの Credential を作成し、プライベートコンテナレジストリ上のイメージをその Credential と共に Execution Environment として追加しています。\nプライベートレジストリ registry.example.com は、前述のとおり 自己署名証明書の HTTPS で、認証も必要 です。つまり、Execution Node 上で手動でプルしようとしても、当然ながら特別な設定なしには次のように 失敗 します。\n# 自己署名証明書なので、明示的に insecure を指定しないと接続できない。 # insecure を指定したとしても、認証が必要なためログインしなければ接続できない。 $ podman pull registry.example.com/ansible/ee:2.12-custom Trying to pull registry.example.com/ansible/ee:2.12-custom... Error: initializing source docker://registry.example.com/ansible/ee:2.12-custom: pinging container registry registry.example.com: Get \u0026#34;https://registry.example.com/v2/\u0026#34;: x509: certificate signed by unknown authority では、最初のテストで利用した ansible.builtin.pause を含む Job Template に、Instance Group とプライベートレジストリ上の Execution Environment を指定してジョブを実行します。\n実行すると、イメージがプルできずに失敗することもなく、ジョブは何の問題もなく開始し、完了します。\nExecution Node 上では、プライベートレジストリ上のイメージが正しくプルされています。\n$ podman events --filter event=pull --since 1h ... 2022-10-07 08:59:17.874383265 +0900 JST image pull registry.example.com/ansible/ee:2.12-custom ... $ podman images REPOSITORY TAG IMAGE ID CREATED SIZE quay.io/ansible/awx-ee latest 8982fdafc038 12 hours ago 1.96 GB registry.example.com/ansible/ee 2.12-custom 196d9b9302d1 21 hours ago 1.18 GB というわけで、AWX 側の設定に応じて、証明書検証も認証もなにやらよしなにやってくれていそうです。もう少し踏み込んで確認します。\n実装上、AWX は、指定された Execution Environment に Credentials が設定されている場合、Ansible Runner にその情報を渡し、さらに Ansible Runner は、受け取った認証情報や設定を /tmp 下に auth.json と registries.conf として保存 して、それを Podman に渡し ています。\n実行されている podman run コマンドの引数を見ると、--authfile が指定 されていることがわかります。この引数は、AWX から認証情報が渡された場合のみ指定されるものです（以下の出力例は見やすいように改行を加えています）。\n$ ps -ef | grep \u0026#34;podman run\u0026#34; awx 58712 58704 0 08:59 pts/0 00:00:00 /usr/bin/podman run --rm --tty --interactive --workdir /runner/project -v /tmp/awx_17_3za59d3w/:/runner/:Z --authfile=/tmp/ansible_runner_registry_17_dya5ku7p/auth.json -v /etc/pki/ca-trust/:/etc/pki/ca-trust/:O -v /usr/share/pki/:/usr/share/pki/:O --env-file /tmp/awx_17_3za59d3w/artifacts/17/env.list --quiet --name ansible_runner_17 --user=root --network slirp4netns:enable_ipv6=true --pull=missing registry.example.com/ansible/ee:2.12-custom ansible-playbook -u root -i /runner/inventory/hosts -e @/runner/env/extravars runner/project/demo.yml --authfile で指定されているファイルの中身には、レジストリの認証情報 がいつもの JSON 形式で保存されています。Base64 でエンコードされていますが、デコードすれば平文が得られます。これが、プライベートレジストリに対して認証を行える仕組みです。\n$ cat /tmp/ansible_runner_registry_17_dya5ku7p/auth.json { \u0026#34;auths\u0026#34;: { \u0026#34;registry.example.com\u0026#34;: { \u0026#34;auth\u0026#34;: \u0026#34;cmVndXNlcjpSZWdpc3RyeTEyMyE=\u0026#34; } } } $ echo \u0026#34;cmVndXNlcjpSZWdpc3RyeTEyMyE=\u0026#34; | base64 -d reguser:Registry123! また、AWX 側で Verify SSL をオフ にした場合、この podman のプロセスには Podman が読み込む設定ファイルのパスを示す環境変数 CONTAINERS_REGISTRIES_CONF と REGISTRIES_CONFIG_PATH が設定 されます（二つあるのは Podman の 3.1.0 以降とそれ未満の両方のバージョンに対応するためのようです）。この環境変数によって、そのジョブ限りの一時的な registries.conf が渡され、そこに記述された insecure = true が機能 します。\n$ cat /proc/58712/environ --show-nonprinting | sed \u0026#39;s/\\^@/\\n/g\u0026#39; | grep REGISTRIES CONTAINERS_REGISTRIES_CONF=/tmp/ansible_runner_registry_17_dya5ku7p/registries.conf REGISTRIES_CONFIG_PATH=/tmp/ansible_runner_registry_17_dya5ku7p/registries.conf $ cat /tmp/ansible_runner_registry_17_dya5ku7p/registries.conf [[registry]] location = \u0026#34;registry.example.com\u0026#34; insecure = true こうして、特に Podman 側の構成を手動で変更することなく、AWX 側での指定のみでプライベートコンテナレジストリ上の EE イメージをプルできるようになっています。\n補足 Execution Node 用の証明書の中身 インストールバンドルには、Execution Node で Receptor が使う証明書と秘密鍵が含まれています。これは、Receptor の CA で署名されています。これの有効期限が固定で 10 日間であることが、前述の注意事項の根拠です。AWX の次のリリースではこの有効期限は 10 年に延長されます。\n$ openssl x509 -text -in receptor/tls/receptor.crt -noout Certificate: Data: ... Issuer: CN = awx Receptor Root CA Validity Not Before: Oct 17 23:18:26 2022 GMT Not After : Oct 27 23:18:26 2022 GMT ... Subject: CN = exec01.ansible.internal ... X509v3 extensions: X509v3 Subject Alternative Name: DNS:exec01.ansible.internal, othername:\u0026lt;unsupported\u0026gt; ... Receptor の CA 自体の証明書も含まれています。デフォルトでは自己署名証明書で、こちらは 10 年間の有効期限があります。\n$ openssl x509 -text -in receptor/tls/ca/receptor-ca.crt -noout Certificate: Data: ... Issuer: CN = awx Receptor Root CA ... Subject: CN = awx Receptor Root CA ... 前述のように、CA の証明書は Kubernetes 上に Secret リソースとして作成することで任意のものに置き換えられる ようです。なお、Execution Node の構成後に後から CA の証明書を置き換える際は、全 Execution Node でインストールバンドルの再生成と再実行が必要になるようです。\n期限が近付いた証明書の更新 Execution Node の証明書を正攻法で更新する場合は、次の手順で対応できます。Receptor の CA 証明書を自前のものに置き換えた場合の手順も同じです。\nAWX 上で当該 Execution Node 用のインストールバンドルを再ダウンロードする 初期インストール時と同様に、インストールバンドル中のプレイブックを実行する Execution Node で Receptor サービスを再起動する（sudo systemctl restart receptor） 手順 3 の手動でのサービス再起動が必要なのが面倒ですが、手順 2 のプレイブックで対応させる（正確にはプレイブックが利用しているコレクションで対応させる）ための Issue と PR が既に作成されているので、近い将来には不要になると考えられます。\nExecution Node の削除 AWX 側からは、インスタンスを登録した画面で Remove ボタンから削除できます。\nただし、Execution Node 上でプレイブックによって構成された諸々のアンインストールは行われないので、Receptor サービスの停止やアンインストールなど、原状回復は手動での作業が必要です。\nreceptorctl による確認と調査 デフォルトではインストールされていませんが、Receptor の CLI である receptorctl を導入すると、Receptor の状況や動作確認をもう一歩踏み込んで行えます。\nAWX 側では、awx-ee コンテナに導入するとよいでしょう（永続化されないので、Pod の再作成できれいな状態に戻せます）。Execution Node 側では、素直に pip を使いますが、awx ユーザに su してから作業すると後続の作業で困りません。\n# AWX 側への receptorctl の導入 kubectl -n awx exec deploy/awx -c awx-ee -- pip install receptorctl # Execution Node 側への receptorctl の導入（仮想環境作成は省略） sudo su - awx pip install receptorctl 導入できたら、次のコマンドで、メッシュネットワークの状況を確認できます。\n# AWX 側での実行例 $ kubectl -n awx exec deploy/awx -c awx-ee -- /home/runner/.local/bin/receptorctl --socket /var/run/receptor/receptor.sock status Warning: receptorctl and receptor are different versions, they may not be compatible Node ID: awx-667b7b7b54-bwhmv Version: 1.2.0+g3213360 System CPU Count: 4 System Memory MiB: 7762 Connection Cost exec01.ansible.internal 1 Known Node Known Connections awx-667b7b7b54-bwhmv exec01.ansible.internal: 1 exec01.ansible.internal awx-667b7b7b54-bwhmv: 1 Route Via exec01.ansible.internal exec01.ansible.internal Node Service Type Last Seen Tags awx-667b7b7b54-bwhmv control Stream 2022-10-07 01:32:20 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} exec01.ansible.internal control StreamTLS 2022-10-07 01:31:33 {\u0026#39;type\u0026#39;: \u0026#39;Control Service\u0026#39;} Node Work Types awx-667b7b7b54-bwhmv local, kubernetes-runtime-auth, kubernetes-incluster-auth Node Secure Work Types exec01.ansible.internal ansible-runner # Execution Node 側で実行する場合のコマンド（出力例は省略） $ receptorctl --socket /var/run/receptor/receptor.sock status 細かい説明は割愛しますが、次のコマンドで AWX 側から Execution Node 側への疎通確認も可能です。AWX での Run health check もほぼ同じことをしています。\n# Execution Node 側で ansible-runner worker --worker-info を実行して結果を取得する $ kubectl -n awx exec deploy/awx -c awx-ee -- \\ /home/runner/.local/bin/receptorctl \\ --socket /var/run/receptor/receptor.sock \\ work submit \\ -f \\ --rm \\ --node exec01.ansible.internal \\ --tls-client tlsclient \\ --signwork \\ ansible-runner \\ -n \\ -a params=\u0026#34;--worker-info\u0026#34; {cpu_count: 4, errors: [], mem_in_bytes: 8139796480, runner_version: 2.2.1, uuid: 527e22aa-286b-4e07-bc1c-e7e0ec5ac7e3} (BMq8X00P, released) さらに掘り下げたい場合は、Receptor のドキュメント を参照するとよいでしょう。\nトラブルシュート Execution Node が正常に追加できない場合の確認手段をいくつか紹介します。\nReceptor サービスの起動状態 前提として、双方の Receptor サービスが起動している必要があります。\nAWX 側 AWX 側では、Receptor のプロセスは AWX の Pod の awx-ee コンテナで動作しています。\nReceptor のプロセスが動作していないとそもそも AWX の Pod が Running にならないため、AWX 自体が動作していれば Receptor も動作していると判断できます。\nExecution Node 側 Execution Node 側では、Receptor は systemd で管理されます。起動状態は systemctl で確認できます。\nsudo systemctl status receptor 設定ファイルの編集後や証明書の更新後などは、再起動が必要です。\nsudo systemctl restart receptor Receptor のログ Receptor のメッシュネットワークを構成するノード間のやりとりは、Receptor のログから確認できます。\nAWX 側 AWX 側の Receptor のログは、AWX の Pod の awx-ee コンテナのログとして確認できます。デフォルトで debug レベルのログが出力されています。\nkubectl -n awx logs -f deploy/awx -c awx-ee 名前解決ができなかったりポートが閉じていたりして何らかの理由で Execution Node に到達できない場合や、証明書に含まれるホスト名が不正な場合、証明書の有効期限が切れている場合などは、このログから確認できます。\nExecution Node 側 Execution Node 側では、Receptor のログは /var/log/receptor/receptor.log に出力されます。\ntail -f /var/log/receptor/receptor.log ただし、デフォルトで info レベルの出力のため、やや粒度が荒い状態です。より詳細なログを確認したい場合は、/etc/receptor/receptor.conf の log-level を debug にし、Receptor サービスを再起動します。\n$ sudo cat /etc/receptor/receptor.conf ... - log-level: debug ... $ sudo systemctl status receptor なお、systemd 管理下にあるため、ジャーナルログも確認できますが、有益な情報はほとんど含まれないようです。\nsudo journalctl -u receptor Receptor 経由で起動されたプロセスの標準入出力 AWX で使われる Receptor では、Receptor 経由で ansible-runner コマンドが起動できるように構成されていますが、ansible-runner コマンド自体の出力は、前述の Receptor のログには含まれません。Receptor としてのメッシュネットワークの構成が正しくても、それ経由で起動された ansible-runner でエラーが発生している場合、Receptor のログだけでは判別は困難です。\nReceptor は、ノード間でやりとりするデータ、すなわち起動されるプロセスの標準入出力のダンプを、一時ディレクトリに保存します。Receptor 経由で起動されたプロセスのエラーが疑われる場合、これを確認すると原因を探索する助けになることがあります。典型的には、次のようなシーンです。\nAWX 側で Execution Node が Unavailable だ AWX 側で Run health check をしても、exit status 1 としか表示されず詳細がわからない Receptor のログにも助けになる情報がない 一時ディレクトリは、AWX 側（awx-ee コンテナ）、Execution Node 側とも共通で、既定で /tmp/receptor/\u0026lt;ノード名\u0026gt;/\u0026lt;ユニット ID\u0026gt; です。ファイル stdin と stdout に標準入力と標準出力がダンプされます。以下は Execution Node 側の出力例です。\n$ sudo ls -lR /tmp/receptor/exec01.ansible.internal /tmp/receptor/exec01.ansible.internal: total 0 drwx------. 2 awx awx 66 Oct 7 08:59 ZKLrOH9m /tmp/receptor/exec01.ansible.internal/ZKLrOH9m: total 248 -rw-------. 1 awx awx 182 Oct 7 08:59 status -rw-------. 1 awx awx 0 Oct 7 08:59 status.lock -rw-------. 1 awx awx 118050 Oct 7 08:59 stdin -rw-------. 1 awx awx 78388 Oct 7 08:59 stdout ただし、ユニット ID はやりとりが発生するごとに変わる上、AWX 経由で発生した処理では終了後にユニット ID のディレクトリも即座に削除されてしまうため、このファイルが確認できる時間は非常に短期間です。\nこのため、ユニット ID の都度確認はせずに、タイミングを見計らって次のようなコマンドで再帰で全ファイルを舐めるとラクです。例えば、AWX 側で Run health check をしてすぐに Execution Node 側でこれを実行すれば、ansible-runner の出力も確認できるでしょう。\ngrep -R \u0026#34;\u0026#34; /tmp/receptor/\u0026lt;ノード名\u0026gt; 以下は無理やり ansible-runner をコケさせたときの例です。具体的なコケ方を Python の Traceback まで追いかけられることがわかります（この例では、/home/awx のパーミッションがおかしい状態です）。\n$ sudo grep -R \u0026#34;\u0026#34; /tmp/receptor/exec01.ansible.internal /tmp/receptor/exec01.ansible.internal/FW69cPcD/status:{\u0026#34;State\u0026#34;:3,\u0026#34;Detail\u0026#34;:\u0026#34;exit status 1\u0026#34;,\u0026#34;StdoutSize\u0026#34;:630,\u0026#34;WorkType\u0026#34;:\u0026#34;ansible-runner\u0026#34;,\u0026#34;ExtraData\u0026#34;:null} /tmp/receptor/exec01.ansible.internal/FW69cPcD/stdout:Traceback (most recent call last): /tmp/receptor/exec01.ansible.internal/FW69cPcD/stdout: File \u0026#34;/usr/local/bin/ansible-runner\u0026#34;, line 8, in \u0026lt;module\u0026gt; /tmp/receptor/exec01.ansible.internal/FW69cPcD/stdout: sys.exit(main()) /tmp/receptor/exec01.ansible.internal/FW69cPcD/stdout: File \u0026#34;/usr/local/lib/python3.9/site-packages/ansible_runner/__main__.py\u0026#34;, line 745, in main /tmp/receptor/exec01.ansible.internal/FW69cPcD/stdout: uuid = ensure_uuid() /tmp/receptor/exec01.ansible.internal/FW69cPcD/stdout: File \u0026#34;/usr/local/lib/python3.9/site-packages/ansible_runner/utils/capacity.py\u0026#34;, line 31, in ensure_uuid /tmp/receptor/exec01.ansible.internal/FW69cPcD/stdout: if uuid_file_path.exists(): /tmp/receptor/exec01.ansible.internal/FW69cPcD/stdout: File \u0026#34;/usr/lib64/python3.9/pathlib.py\u0026#34;, line 1424, in exists /tmp/receptor/exec01.ansible.internal/FW69cPcD/stdout: self.stat() /tmp/receptor/exec01.ansible.internal/FW69cPcD/stdout: File \u0026#34;/usr/lib64/python3.9/pathlib.py\u0026#34;, line 1232, in stat /tmp/receptor/exec01.ansible.internal/FW69cPcD/stdout: return self._accessor.stat(self) /tmp/receptor/exec01.ansible.internal/FW69cPcD/stdout:PermissionError: [Errno 13] Permission denied: \u0026#39;/home/awx/.ansible_runner_uuid\u0026#39; まとめ AWX 21.7.0 でアナウンスされた Execution Node の概要と構成方法、使い方を簡単に紹介しました。構成の幅が広がりそうなおもしろい機能です。\n大規模な環境で拡張性を向上させるだけでなく、小規模な環境であっても隔離ネットワークへの踏み台としての使い道はありそうです。\n前述のとおり、21.7.0 の段階では、証明書の期限の問題があるため本格的な活用は少し難しいかもしれませんが、次のリリース以降では安心して使えるようになりそう（追記： この問題は 21.8.0 で修正されました）なので、要件に応じて便利に使っていけるとよいですね。\n","date":"2022-10-07T22:01:31Z","image":"/archives/4467/img/image-248.jpg","permalink":"/archives/4467/","title":"AWX に Kubernetes クラスタ外のスタンドアロン実行ノードを追加する"},{"content":"はじめに Azure Kubernetes Service (AKS) 上の AWX で、ジョブやインベントリの同期を実行したとき、それにおおむね 4 分から 5 分程度以上の待ち時間 を要するタスクが含まれると、ジョブが失敗してしまうことがあるようです。\nAWX の Issue として報告されている事象 で、もろもろ調べたり試したりした結果、なんやかんやで現時点では わりとダーティハック感の強いワークアラウンド しかなさそうな気配があったため、簡単に紹介です。\n例えば、次のような シンプル極まりないプレイブック を動かすだけでも、AKS 上の AWX では 途中で失敗 してしまうことがあります。\n--- - hosts: localhost gather_facts: false tasks: - ansible.builtin.pause: minutes: 10 追記： この問題は AWX 21.14.0 で解決されました。詳細は最下部で紹介しています。\n状況の判断 この問題は、次の条件が揃うと発生する可能性があります。\nAzure Kubernetes Service（AKS）上に AWX を構築している Kubernetes 1.22.11（1.23.x、1.24.x でも再現報告あり） AWX 21.3.0（おそらくバージョンとは無関係に再現する） デフォルトの EE（quay.io/ansible/awx-ee:latest） この AWX で、以下のいずれかを実行する 完了まで 4 分以上を要するタスクを含んだジョブ 完了まで 4 分以上を要するインベントリの同期 冒頭の例では、ansible.builtin.pause に 10 分かかるため、この条件に合致しています。\nこの問題が発生すると、ジョブは エラー のステータスで終了しますが、ジョブのログには特に何も記録されません。\nDetails タブでは、エラーの理由として Job terminated due to error が記録されます。\n原因と対応方針 原因 子の挙動は、AKS 内で Kubernetes の API サーバの TCP 通信 に 4 分の暗黙のアイドルタイムアウト があり、AWX と EE の間の通信が切断されてしまうことに起因しているようです。関連 Issue は #1052、#1755、#1877 あたりです。\nAWX でジョブやインベントリの同期が実行されるとき、内部的にはおおまかには次のような処理が行われています。\nEE の Pod が作成される AWX の Pod の awx-task コンテナが、Kubernetes の API サーバに接続 して、EE の Pod のログのストリームを開く EE の Pod でプレイブックが実行され、実行状況や結果が標準出力に逐次流される プレイブックの実行状況や結果が、Kubernetes の API サーバからのログストリーム経由で AWX の Pod の awx-task コンテナに届く つまり、AWX の awx-task コンテナは、ジョブの実行開始から終了まで Kubernetes の API サーバとのログのストリーミング通信を開きっぱなし にします。その途中で EE のログに動きがない時間が 4 分以上続く と、AKS のアイドルタイムアウトの閾値を超えて TCP の接続が TCP RST パケットとともに切断されてしまい、ジョブが中断されてしまうということのようです。\n対応方針 根本的には AKS 側でこのアイドルタイムアウトの時間を延ばせればよいのですが、前述の関連 Issue やドキュメントを見る限り、どうもユーザが触れる範囲のなにかではなさそうな雰囲気があります。AKS の仕様とアーキテクチャにはそこまで明るくないのであまり自信がないですが、LB の仕様に関するドキュメント はあるものの、API サーバの手前にいるのは Azure リソースとして見える Azure Standard Load Balancer（タイムアウトはデフォルトで 30 分）とは別物と思われ、手出しはできなそうです（この部分、識者からのつっこみに期待しています）。\nそんなわけで、AKS 側での対処はいったんあきらめてユーザ側（クライアント側）でどうにかすることを考える必要がありますが、正攻法として考えられる TCP のキープアライブの明示的な利用 や TCP RST パケット受信後のストリームの再接続 は、現状の AWX（というか Ansible Runner、もっといえば Receptor）では、残念ながら実装されていません。\nというわけで、現時点では どうにかして Kubernetes の API サーバと awx-task コンテナ間で定期的に通信を発生させる、すなわち EE の Pod にどうにかして定期的にログを吐かせる ことがワークアラウンドになりそうです。\n対応 あからさまにダーティハック感がすごいので、ほかにもっとよい方法がある気がしてならないのですが、\nEE のエントリポイントのスクリプトを修正して awx-task が受け取っても安全に無視される文字列を 無限ループで定期的に標準出力に吐かせる なことを考えます。\nエントリポイント用スクリプト こんな感じの中身のファイルをファイル名 entrypoint で作ります。元のエントリポイントのスクリプトに、\u0026amp; 付きの while ループの 4 行（以下の例ではハイライトしている 48 行目以降）を足したものです。\nコンテナ内のプロセスが増えることにはなりますが、本来の PID 1 とは無関係に動くため、コンテナ全体の動きには影響はありません。\n#!/usr/bin/env bash # In OpenShift, containers are run as a random high number uid # that doesn\u0026#39;t exist in /etc/passwd, but Ansible module utils # require a named user. So if we\u0026#39;re in OpenShift, we need to make # one before Ansible runs. if [[ (`id -u` -ge 500 || -z \u0026#34;${CURRENT_UID}\u0026#34;) ]]; then # Only needed for RHEL 8. Try deleting this conditional (not the code) # sometime in the future. Seems to be fixed on Fedora 32 # If we are running in rootless podman, this file cannot be overwritten ROOTLESS_MODE=$(cat /proc/self/uid_map | head -n1 | awk \u0026#39;{ print $2; }\u0026#39;) if [[ \u0026#34;$ROOTLESS_MODE\u0026#34; -eq \u0026#34;0\u0026#34; ]]; then cat \u0026lt;\u0026lt; EOF \u0026gt; /etc/passwd root:x:0:0:root:/root:/bin/bash runner:x:`id -u`:`id -g`:,,,:/home/runner:/bin/bash EOF fi cat \u0026lt;\u0026lt;EOF \u0026gt; /etc/group root:x:0:runner runner:x:`id -g`: EOF fi if [[ -n \u0026#34;${LAUNCHED_BY_RUNNER}\u0026#34; ]]; then RUNNER_CALLBACKS=$(python3 -c \u0026#34;import ansible_runner.callbacks; print(ansible_runner.callbacks.__file__)\u0026#34;) # TODO: respect user callback settings via # env ANSIBLE_CALLBACK_PLUGINS or ansible.cfg export ANSIBLE_CALLBACK_PLUGINS=\u0026#34;$(dirname $RUNNER_CALLBACKS)\u0026#34; fi if [[ -d ${AWX_ISOLATED_DATA_DIR} ]]; then if output=$(ansible-galaxy collection list --format json 2\u0026gt; /dev/null); then echo $output \u0026gt; ${AWX_ISOLATED_DATA_DIR}/collections.json fi ansible --version | head -n 1 \u0026gt; ${AWX_ISOLATED_DATA_DIR}/ansible_version.txt fi SCRIPT=/usr/local/bin/dumb-init # NOTE(pabelanger): Downstream we install dumb-init from RPM. if [ -f \u0026#34;/usr/bin/dumb-init\u0026#34; ]; then SCRIPT=/usr/bin/dumb-init fi while /bin/true; do sleep 120 echo \u0026#39;{\u0026#34;event\u0026#34;: \u0026#34;FLUSH\u0026#34;, \u0026#34;uuid\u0026#34;: \u0026#34;keepalive\u0026#34;, \u0026#34;counter\u0026#34;: 0, \u0026#34;end_line\u0026#34;: 0}\u0026#39; \u0026gt; /proc/1/fd/1 done \u0026amp; exec $SCRIPT -- \u0026#34;${@}\u0026#34; 使う EE のイメージのバージョンによっては、元ネタにするべきエントリポイントのスクリプトも変わります。実際のイメージの中から kubectl cp で持ってくる（/usr/bin/entrypoint がそれです）か、ansible/ansible-runner リポジトリから適切なバージョンの元ネタのファイル を持ってくるとよいでしょう。\nwhile ループでは、120 秒ごとに JSON 文字列を echo させています。echo 先は /proc/1/fd/1 で、つまり、コンテナのログとして扱われる、PID 1 の標準出力です。ここに吐いた文字列は、containerd から Kubernetes の API を経由して awx-task コンテナに伝わります。\n詳細は割愛しますが、この JSON 文字列は Ansible Runner の Process フェイズ（AWX では awx-task 側）が受け取っても無害なように工夫してあります。逆に言えば、Process フェイズは受け取ったログの全行が所定のスキーマの JSON 文字列であることを暗黙に期待しているため、そのスキーマではない任意の文字列を送ってしまうと、うまく処理できずにジョブが失敗するか例外を吐くかで、期待した結果が得られません。\nConfigMap の作成 このスクリプトをもとに、EE が起動する Namespace に ConfigMap を作ります。\nkubectl -n awx create configmap awx-runner-entrypoint --from-file=entrypoint Container Group の作成 AWX 内で Container Group を作って、Custom pod spec として次の YAML 文字列をつっこみます。デフォルトのモノに volumes と volumeMounts を追加したものです（ベースにするべきデフォルトも AWX のバージョンで変わります）。\napiVersion: v1 kind: Pod metadata: namespace: awx spec: serviceAccountName: default automountServiceAccountToken: false containers: - image: quay.io/ansible/awx-ee:latest name: worker args: - ansible-runner - worker - \u0026#39;--private-data-dir=/runner\u0026#39; resources: requests: cpu: 250m memory: 100Mi volumeMounts: - mountPath: /usr/bin/entrypoint name: awx-runner-entrypoint-volume subPath: entrypoint volumes: - name: awx-runner-entrypoint-volume configMap: name: awx-runner-entrypoint items: - key: entrypoint path: entrypoint mode: 0775 これで、デフォルトのエントリポイントスクリプトが、前述の手順で作った ConfigMap 内の内容で置き換えられます。\nJob Template の Instance Group の変更 Job Template で Instance Group として前述の手順で作成した Container Group を指定します。\n結果 EE の Pod のログに、120 秒ごとにダミー文字列が吐かれるようになります。\n$ kubectl -n awx logs -f automation-job-5-vr8v7 ... {\u0026#34;uuid\u0026#34;: \u0026#34;5e8c23a8-8e7f-44c1-814c-f0ce1974eb02\u0026#34;, \u0026#34;counter\u0026#34;: 8, \u0026#34;stdout\u0026#34;: \u0026#34;(ctrl+C then \u0026#39;C\u0026#39; = continue early, ctrl+C then \u0026#39;A\u0026#39; = abort)\\r\u0026#34;, \u0026#34;start_line\u0026#34;: 7, \u0026#34;end_line\u0026#34;: 8, \u0026#34;runner_ident\u0026#34;: \u0026#34;5\u0026#34;, \u0026#34;event\u0026#34;: \u0026#34;verbose\u0026#34;, \u0026#34;job_id\u0026#34;: 5, \u0026#34;pid\u0026#34;: 28, ...} {\u0026#34;event\u0026#34;: \u0026#34;FLUSH\u0026#34;, \u0026#34;uuid\u0026#34;: \u0026#34;keepalive\u0026#34;, \u0026#34;counter\u0026#34;: 0, \u0026#34;end_line\u0026#34;: 0} {\u0026#34;event\u0026#34;: \u0026#34;FLUSH\u0026#34;, \u0026#34;uuid\u0026#34;: \u0026#34;keepalive\u0026#34;, \u0026#34;counter\u0026#34;: 0, \u0026#34;end_line\u0026#34;: 0} {\u0026#34;event\u0026#34;: \u0026#34;FLUSH\u0026#34;, \u0026#34;uuid\u0026#34;: \u0026#34;keepalive\u0026#34;, \u0026#34;counter\u0026#34;: 0, \u0026#34;end_line\u0026#34;: 0} {\u0026#34;event\u0026#34;: \u0026#34;FLUSH\u0026#34;, \u0026#34;uuid\u0026#34;: \u0026#34;keepalive\u0026#34;, \u0026#34;counter\u0026#34;: 0, \u0026#34;end_line\u0026#34;: 0} {\u0026#34;event\u0026#34;: \u0026#34;FLUSH\u0026#34;, \u0026#34;uuid\u0026#34;: \u0026#34;keepalive\u0026#34;, \u0026#34;counter\u0026#34;: 0, \u0026#34;end_line\u0026#34;: 0} {\u0026#34;uuid\u0026#34;: \u0026#34;9a4afe85-fa1f-4096-a2ab-897f8dcf89ef\u0026#34;, \u0026#34;counter\u0026#34;: 9, \u0026#34;stdout\u0026#34;: \u0026#34;\\u001b[0;32mok: [localhost]\\u001b[0m\u0026#34;, \u0026#34;start_line\u0026#34;: 8, \u0026#34;end_line\u0026#34;: 9, \u0026#34;runner_ident\u0026#34;: \u0026#34;5\u0026#34;, \u0026#34;event\u0026#34;: \u0026#34;runner_on_ok\u0026#34;, \u0026#34;job_id\u0026#34;: 5, \u0026#34;pid\u0026#34;: 22, ...} ... この文字列は Kubernetes の API サーバを通じて awx-task にも届くため、awx-task が開いたストリームには定期的に通信が発生することになり、タイムアウトを迎えることがなくなります。\nなお、標準出力のロックは Ansible Runner の awx_display コールバックプラグインの中でハンドリングされているようで、ざっとコードや実機で確認した範囲では、Ansible Runner の本来の出力とエントリポイント中の echo が衝突してログの JSON 文字列が破壊されるような事故は起きないように見えています（たぶん）。\nまとめ AKS 上に AWX をデプロイして使うときのダーティハックを紹介しました。\nもっとよい方法がありそう（あるいはユーザ側でなく Ansible Runner 側か Receptor 側で解決されるべき）なので、何か思いついたら（または実装で解決できたら、もしくはされたら）更新します。\n追記： AWX 21.14.0 での恒久対策 AWX の 21.14.0 で、本問題への恒久対策が実装されました。より具体的には、Ansible Runner の 2.3.2 でキープアライブメッセージを定期的に送信する機能が実装され、AWX の 21.14.0 でその機能を使えるようになった形です。\n以下を充足することで、この機能を有効化できます。\nAWX を 21.14.0 以降にアップグレードする ジョブ用の EE イメージとして、最新の quay.io/ansible/awx-ee:latest、または quay.io/ansible/awx-ee:21.14.0 以降を利用する AWX の Settings \u0026gt; Job settings の K8S Ansible Runner Keep-Alive Message Interval で、キープアライブメッセージの送信間隔を 1 以上に設定する ","date":"2022-08-13T06:26:11Z","image":"/archives/4441/img/image-248.jpg","permalink":"/archives/4441/","title":"AKS 上の AWX で実行時間の長いジョブが失敗するときの対処"},{"content":"はじめに Ansible で Windows ホストに対してプレイブックを実行したい場合、典型的にはホストへの接続には WinRM を利用します。この際、対象の Windows ホストに そのホストのローカルユーザで認証 する場合は、特別な工夫をしなくてもユーザ名とパスワードを使った Basic 認証や CredSSP 認証が利用できます。\n一方で、対象の Windows ホストが Windows ドメインに参加していて、Ansible から ドメインユーザ で認証したい場合、WinRM の接続には Kerberos 認証 が推奨されており、そのためにはあらかじめ Ansible の実行ホストで Kerberos クライアントが正しく構成されている必要があります。\nWindows Remote Management - Kerberos Ansible を ansible-playbook で直接実行する場合はそこまでハードルは高くないですが、AWX で実行するジョブで Kerberos 認証を使いたい 場合は、わりと混乱しがちです。Ansible Automation Controller のドキュメント に手順があるものの、あくまで AAC 用であって AWX には適合しない ものです。\n本エントリでは、AWX で Kerberos 認証を使って Windows ホストに接続する 方法を紹介します。\n環境 本エントリでは、Kubernetes 上で動作する AWX を前提としています。AWX のバージョンは本エントリでは 21.2.0 を使っていますが、Execution Environment（以下 EE）が登場した 18 以降であればおそらく同じ手順になるものと推測しています。AWX のデプロイ方法は 別のエントリ で過去に紹介しています。\nまた、エントリ中で扱うドメインその他の情報は以下の通りです。必要に応じて適宜読み替えてください。\n項目 値 ドメイン名 kurokobo.internal ドメインコントローラ kuro-ad01.kurokobo.internal KDC サーバ kuro-ad01.kurokobo.internal 操作対象の Windows ホスト（ドメインメンバ） kuro-win01.kurokobo.internal 接続に利用するドメインユーザ awx@kurokobo.internal 全体像 技術的な背景の整理 作ろうとしている姿の意味は、次の技術的な仕様を把握できていると理解しやすくなります。\nAWX でジョブを実行すると、実行するたびに新しい Pod が EE として作成される Kerberos 認証はジョブの実行のたびに EE の中で行われる EE の Pod はジョブが終了すると削除される つまり、AWX で Kerberos 認証を使うには、AWX 自体のインスタンスではなく、都度作成される EE が Kerberos 認証を行えるように正しく構成されている状態 を実現する必要があります。技術的には、これは次の状態です。\nEE に Kerberos クライアントとしての動作に必要なパッケージがインストールされている EE の /etc/krb5.conf が正しく構成されている 前者は、デフォルトで krb5-libs と krb5-workstation が EE のベースイメージに含まれているため、何もしなくても充足できています。しかしながら、後者の /etc/krb5.conf はデフォルトのまま なので、このファイルを何らかの手段でカスタマイズする必要があります。\nこの目的で使える機能として、AWX にはホストの任意のディレクトリを EE に公開する設定（AWX_ISOLATION_SHOW_PATHS、UI では Paths to expose to isolated jobs）があり、これでも動作はしますが、以下の点から Kubernetes 環境での利用はあまり推奨されません（とはいえせっかくなので参考情報としてこの場合の操作方法を本エントリ末尾に記載しています）。\nパスの EE への公開に hostPath が利用されるため、Kubernetes クラスタのうち、EE が実行されうる全ノードのローカルにファイルを配置して管理する必要がある 特定の EE のみへの設定はできず、問答無用ですべての EE が同じ hostPath を使うようになる そもそも hostPath の利用は Kubernetes の世界ではセキュリティリスクの観点で利用は避けるべきとされている そこで今回は、以前に別エントリで紹介した Container Group を使い、EE の Pod の仕様をカスタマイズ して ConfigMap 化した krb5.conf をマウントする ことで対応します。\nなお、krb5.conf を含んだ状態の EE のイメージを Ansible Builder でビルドする 手も別解として考えられますが、EE のイメージの汎用性が損なわれる点、トライアンドエラーがしにくくなる点などから、今回は採用していません。\n手順の概要 前述の目的を達成するため、以下の作業を行います。\n操作対象の Windows ホストの構成 Kerberos 認証を受け入れるよう WinRM を構成する 接続に利用するドメインユーザに WinRM での操作権限を付与する Kubernetes の構成 Kubernetes 上の ConfigMap リソースとして krb5.conf を作成する AWX の構成 /etc/krb5.conf を ConfigMap からマウントするようにした Container Group を作成する 接続に利用するドメインユーザの Credential を作成する 操作対象の Windows ホストを Inventory に作成する Job Template に Container Group、Credential、Inventory を設定する 手順 実際の手順です。同じ情報は GitHub にも置いてあります。\n操作対象の Windows ホストの構成 WinRM の構成 Ansible のドキュメント を参考に、WinRM を有効化します。台数が多い場合は GPO でバラまくのが常套手段ですが、手でやる場合は winrm quickconfig を叩くのがラクです。\nなお、Kerberos 自体がペイロードを暗号する仕組みを持っている ため、WinRM の待ち受けは HTTP でも安全です。ドキュメント によると、HTTPS 越しの Kerberos も利用できますが、この場合は TLS での暗号化のみ実施され、Kerberos は暗号化をしなくなるようです（未検証ですが、変数 ansible_winrm_message_encryption を always にすれば暗号化を二重にさせることもできるようです）。\n今回は HTTP のみとして進めます。WinRM の Listener の状態は以下で確認できます。\n\u0026gt; winrm enumerate winrm/config/Listener Listener Address = * Transport = HTTP Port = 5985 Hostname Enabled = true URLPrefix = wsman CertificateThumbprint ListeningOn = 127.0.0.1, ... 認証で Kerberos が有効になっていることも併せて確認します。\n\u0026gt; winrm get winrm/config/Service Service Auth Basic = true Kerberos = true Negotiate = true Certificate = false CredSSP = false CbtHardeningLevel = Relaxed ... 権限の構成 WinRM は、デフォルトでは ローカルの Administrators グループ に所属するユーザからのみ接続を受け付けます。このグループ以外のユーザを使いたい場合は、winrm configSDDL default で起動する SDDL の設定画面で 読み取り と 実行 の権限を個別に付与します。詳細は Ansible のドキュメント に記載があります。\nつまり、今回はドメインユーザ awx@kurokobo.internal を利用するため、これを操作対象の Windows ホストの Administrators グループに参加させるか、あるいは、個別に権限を与える操作が必要だということです。\nどちらでも動作はするので、AWX で実行させたいジョブでの管理者権限の要否に合わせて設定するとよさそうです。今回は、グループ追加では芸がないので、後者の SDDL 案にしています。\nKubernetes の構成 krb5.conf の作成 AWX が動作している Kubernetes クラスタに対する kubectl が実行できるホストで、krb5.conf を作成します。このファイルについては、Ansible のドキュメント や Ansible Automation Controller のドキュメントでも触れられています。\n中身は要件次第なので千差万別ですが、例えば最小限だと以下です。設定ファイル中、レルムは大文字 にします。\n[realms] KUROKOBO.INTERNAL = { kdc = kuro-ad01.kurokobo.internal admin_server = kuro-ad01.kurokobo.internal } [domain_realm] .kurokobo.internal = KUROKOBO.INTERNAL kurokobo.internal = KUROKOBO.INTERNAL 雑な説明ですが、レルムは Kerberos の世界観でのドメイン のようなもので、このファイルで Windows の世界の小文字のドメインと Kerberos の世界の大文字のレルムの対応付けを定義 しているようなイメージです。\nConfigMap の作成 krb5.conf ができたら、それを使って Kubernetes クラスタに ConfigMap を作成します。作成先は EE が動作する NameSpace です。この例では、別のエントリ の構成をベースにしているため、Namespace は awx です。\nkubectl -n awx create configmap awx-kerberos-config --from-file=krb5.conf 正常に作成できると、ConfigMap の data に krb5.conf として保存されます。\n$ kubectl -n awx get configmap awx-kerberos-config -o yaml apiVersion: v1 data: krb5.conf: |- [realms] KUROKOBO.INTERNAL = { kdc = kuro-ad01.kurokobo.internal admin_server = kuro-ad01.kurokobo.internal } [domain_realm] .kurokobo.internal = KUROKOBO.INTERNAL kurokobo.internal = KUROKOBO.INTERNAL kind: ConfigMap metadata: ... name: awx-kerberos-config namespace: awx ... AWX の構成 Container Group の作成 AWX 上で Container Group を作成します。Web UI では、Administration の下の Instance Groups で Add し、Customize pod specification にチェックを入れて、以下の YAML 文字列をつっこみます。\napiVersion: v1 kind: Pod metadata: namespace: awx spec: serviceAccountName: default automountServiceAccountToken: false containers: - image: \u0026#39;quay.io/ansible/awx-ee:latest\u0026#39; name: worker args: - ansible-runner - worker - \u0026#39;--private-data-dir=/runner\u0026#39; resources: requests: cpu: 250m memory: 100Mi volumeMounts: - name: awx-kerberos-volume mountPath: /etc/krb5.conf subPath: krb5.conf volumes: - name: awx-kerberos-volume configMap: name: awx-kerberos-config デフォルトの Pod 定義（AWX のバージョンで若干差異があります）を踏襲しつつ、volumes と volumeMounts を足した形です。先の手順で作成した ConfigMap awx-kerberos-config の krb5.conf を /etc/krb5.conf としてマウントさせています。\nこれで、この Container Group を使ってジョブを実行すれば、その EE ではカスタマイズ済みの /etc/krb5.conf が認識できるようになります。\nCredential の構成 AWX 上で、通常の Credential をつくるのと同じ手順で Kerberos 認証に使うユーザ の Machine タイプ の Credential を作成します。\nこのとき、Username は \u0026lt;ユーザ名\u0026gt;@**==\u0026lt;レルム\u0026gt;==** とします。レルム なので、つまり、@ 以降は大文字 にします。\n今回はドメインユーザ awx@kurokobo.internal を使いますが、krb5.conf でドメイン kurokobo.internal はレルム KUROKOBO.INTERNAL に対応づけているため、 入力すべき文字列は awx@KUROKOBO.INTERNAL です。\nUsername に入力した値は、ジョブで Ansible が実行される際、kinit に expect でそのまま渡されます。\nInventory の構成 AWX 上で、Inventory に操作対象の Windows ホストを追加します。このとき、ホストの名前 を IP アドレスではなく FQDN にする必要があります。今回の例では、kuro-win01.kurokobo.internal です。\nまた、ホスト変数（またはグループ変数）で次の値を指定し、Kerberos の利用を明示します（しなくても勝手に使われますが、管理上わかりやすいのであえて書いておくほうが好きです）。\n--- ansible_connection: winrm ansible_winrm_transport: kerberos ansible_port: 5985 5985 は WinRM の HTTP の待ち受けポートです。操作対象の Windows ホスト側で WinRM を HTTPS で構成した場合は 5986 にします（HTTPS が自己署名証明書の場合は構成次第で ansible_winrm_server_cert_validation: ignore も必要です）。\nJob Template の構成 操作対象の Windows ホストに対して実行したいジョブで、以下を構成します。\nInventory には、先の手順で作成した 操作対象が FQDN で登録されている ものを指定します Credential には、先の手順で作成した \u0026lt;ユーザ名\u0026gt;@\u0026lt;レルム（大文字）\u0026gt; のものを指定します Instance Groups には、先の手順で作成した krb5.conf をマウントするようにカスタマイズした Container Group を指定します テスト ここまでの作業が問題なければ、ジョブは成功するはずです。例えば ansible.windows.win_ping だけのプレイブックを流してみるとよいでしょう。\n--- - name: Test Kerberos Authentication hosts: kuro-win01.kurokobo.internal gather_facts: false tasks: - name: Ensure windows host is reachable ansible.windows.win_ping: Job Template の Verbosity を 4 (Connection Debug) にすると、Kerberos のために kinit が呼び出されたことをログで確認できます。\nTASK [Ensure windows host is reachable] **************************************** ... \u0026lt;kuro-win01.kurokobo.internal\u0026gt; ESTABLISH WINRM CONNECTION FOR USER: awx@KUROKOBO.INTERNAL on PORT 5985 TO kuro-win01.kurokobo.internal calling kinit with pexpect for principal awx@KUROKOBO.INTERNAL ... ok: [kuro-win01.kurokobo.internal] =\u0026gt; { \u0026#34;changed\u0026#34;: false, \u0026#34;invocation\u0026#34;: { \u0026#34;module_args\u0026#34;: { \u0026#34;data\u0026#34;: \u0026#34;pong\u0026#34; } }, \u0026#34;ping\u0026#34;: \u0026#34;pong\u0026#34; } トラブルシュート とはいえ、えてしてうまくいかないものです。コケたら愚直にひとつずつ設定を確認していくしかありませんが、こういうプレイブックを流すと少しわかりやすくなります。\n--- - name: Debug Kerberos Authentication hosts: localhost gather_facts: false tasks: - name: Ensure /etc/krb5.conf is mounted ansible.builtin.debug: msg: \u0026#34;{{ lookup( \u0026#39;file\u0026#39;, \u0026#39;/etc/krb5.conf\u0026#39; ) }}\u0026#34; - name: Pause for specified minutes for debugging ansible.builtin.pause: minutes: 10 実行後、ansible.builtin.pause が効いている 10 分間、EE の Bash を触って調査 ができます。EE の Pod（automation-job- で始まる名前の Pod）を特定して、kubectl exec -it します。\n$ kubectl -n awx get pod NAME READY STATUS RESTARTS AGE awx-postgres-0 1/1 Running 0 41h awx-76445c946f-btfzz 4/4 Running 0 41h awx-operator-controller-manager-7594795b6b-565wm 2/2 Running 0 41h automation-job-42-tdvs5 1/1 Running 0 4s $ kubectl -n awx exec -it automation-job-42-tdvs5 -- bash bash-4.4$ もし、そもそも EE が起動する前にジョブが失敗してしまうのであれば、Container Group の設定の誤りか、ConfigMap の設定の誤りが考えられます。\nkrb5.conf のマウントの確認 EE の Bash が触れたら、/etc/krb5.conf に意図したファイルが意図した中身でマウントされていることを確認します。意図していない状態であれば、Job Template か Container Group か ConfigMap の設定の確認が必要です。\nbash-4.4$ cat /etc/krb5.conf [realms] KUROKOBO.INTERNAL = { kdc = kuro-ad01.kurokobo.internal admin_server = kuro-ad01.kurokobo.internal } [domain_realm] .kurokobo.internal = KUROKOBO.INTERNAL KDC への到達性の確認 KDC の名前解決ができることと、ネットワーク的な到達性を確認します。いろいろコマンドが無いうえに sudo もできないのでパッケージも足せませんが、こういうときは Busybox を持ってくるとラクです（ただし busybox ping や busybox traceroute は権限の都合で EE 内では使えません）。\n# Busybox のダウンロードと実行権限の付与 bash-4.4$ curl -o busybox https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox bash-4.4$ chmod +x busybox # 名前解決の確認（ドメイン名） bash-4.4$ ./busybox nslookup kurokobo.internal Server: 10.43.0.10 Address: 10.43.0.10:53 Name: kurokobo.internal Address: ... # 名前解決の確認（KDC） bash-4.4$ ./busybox nslookup kuro-ad01.kurokobo.internal Server: 10.43.0.10 Address: 10.43.0.10:53 Name: kuro-ad01.kurokobo.internal Address: ... # 到達性の確認（88 番ポート） bash-4.4$ ./busybox nc -v -w 1 kuro-ad01.kurokobo.internal 88 kuro-ad01.kurokobo.internal (...:88) open 手動でのチケット発行可否の確認 kinit コマンドを手でたたくと、チケットの発行可否を確認できます。実際のジョブの中でも同等の操作が行われているため、まずはこれが通る状態にするのが重要です。\n# kinit コマンドに \u0026lt;ユーザ名\u0026gt;@\u0026lt;レルム\u0026gt; を渡して実行しパスワードを入力 # レルムは大文字である点に注意 bash-4.4$ kinit awx@KUROKOBO.INTERNAL Password for awx@KUROKOBO.INTERNAL: # エラーなく通ったら発行されたチケットを klist で確認できる bash-4.4$ klist Ticket cache: FILE:/tmp/krb5cc_1000 Default principal: awx@KUROKOBO.INTERNAL Valid starting Expires Service principal 07/02/22 12:32:28 07/02/22 22:32:28 krbtgt/KUROKOBO.INTERNAL@KUROKOBO.INTERNAL renew until 07/03/22 12:32:21 エラーごとの対処方法 GitHub のリポジトリ によくあるエラーと確認ポイントを書いているので、併せてどうぞ。\nまた、Ansible の Kerberos 関連のページにもトラブルシュートについて記載 があり、参考にできます。\n技術的な補足 いくつか余談めいたメモです。\nAWX_ISOLATION_SHOW_PATHS と Container Group デフォルトでは、AWX の AWX_ISOLATION_SHOW_PATHS の設定は、Instance Group に対してのみ機能し、Container Group では機能しません。すなわち、Kubernetes 環境ではデフォルトでは機能しません（推測ですが AAC 向けっぽさがありますね）。Kubernetes 環境でも機能させたい場合は、AWX_MOUNT_ISOLATED_PATHS_ON_K8S（UI では Expose host paths for Container Groups）を true（有効）にする必要があります。\nAWX_ISOLATION_SHOW_PATHS を使って実装する場合は、本エントリの手順は以下のようにもう少し簡略化できます。ただし、全 EE が無条件で同じ hostPath を使うようになるため、副作用やリスクには配慮が必要です。\nConfigMap と Container Group は作成しない EE が実行されうる全ノードの（kubelet がアクセスできる）任意のパスに krb.conf を配置する AWX_MOUNT_ISOLATED_PATHS_ON_K8S（UI では Expose host paths for Container Groups）を true（有効）にする AWX_ISOLATION_SHOW_PATHS（UI では Paths to expose to isolated jobs）に /path/to/local/krb5.conf:/etc/krb5.conf:O を追加する レルムの大文字小文字 MIT Kerberos のドキュメント でも RFC4120 でも、レルム名の大文字と小文字が区別されることは明記されているものの、大文字でなければならないとはされておらず、慣習的（by convention）な推奨（recommended）とされているだけです。\nそんなわけで、/etc/krb5.conf 内を全部小文字にして [libdefaults] で canonicalize を true にする（またはコマンドライン引数で -C を付ける）と、実は kinit でのチケット発行が小文字のレルムでもできるようになります。……が、Windows 側の Kerberos や GSSAPI の実装で小文字が通用する範囲がいまいちわからないので、本エントリではおとなしく慣習に迎合して大文字にしています。\nおわりに AWX で Kerberos 認証を使う方法を紹介しました。大規模な環境では、操作対象側を GPO で設定してしまえば便利に使えそうですね。\n","date":"2022-07-04T17:11:41Z","image":"/archives/4379/img/image-248.jpg","permalink":"/archives/4379/","title":"AWX で Kerberos 認証を使って Windows ホストに接続する"},{"content":"AutoMuteUs が 7.0 にメジャアップデートされ、操作方法が .au やメンション から スラッシュコマンド に変更されました。これによって操作感が大きく変わり、またセルフホストでは関連するオプションがいくつか追加されています。\n本エントリでは、公式ボットサービスの利用者 と セルフホストの利用者 の 双方 を対象に、簡単に変更点とその概要、使い方を紹介します。\n全利用者向けの情報 公式ボットサービスとセルフホストのどちらの利用者にも適用できる情報です。\nアップデートの背景 今回のアップデートは、ひとことで言えば、Discord 側の仕様変更への追従が目的です。\n2021 年 10 月に、大規模に使われているボットでは .au などプレフィックス形式のコマンドが将来的に受け取れなくなる 旨が、Discord 側から予告 されていました。開発者には対策としてスラッシュコマンドなど新しい方式への移行が求められたため、今回、AutoMuteUs もこれに対応したということです。\nスラッシュコマンド化しなくても メンションは引き続き受け取れる旨も案内されていた ため、先日、暫定的にメンションでコマンドを受け付ける修正が行われていました（6.15.3）が、今回のアップデートで、恒久的な対策として、より望ましい形への改修が行われたことになります。\nスラッシュコマンドへの統一 これまでの AutoMuteUs の操作は、次のように .au などの文字列か、あるいはボットへのメンションなど、所定のプレフィックスを先頭に付与してコマンドを実行する形でした。\n.au new @AutoMuteUs new AutoMuteUs の 7.0 から、これが Discord の スラッシュコマンド を使った実装に変更され、プレフィックスでは操作できなくなりました。\n7.0 では、Discord の入力欄で / を入力 するだけで 使えるコマンドが一覧 され、候補から簡単に選択できる ほか、/ の後も続けて入力すれば候補を絞り込めるようになっています。タブ補完 も有効で、その後に続ける パラメータ も 取りうる値に応じて適切な選択肢が表示 されます。\n総じて 手でコマンドを一字一句入力する手間がほとんどない __状態になり、ユーザビリティが大きく向上しています。モバイルアプリケーションからの操作もしやすくなっています。\nキャプチャ用リンクの通達方法の変更 従来の .au new に相当する /new の実行時、従来は DM でキャプチャ用のリンクが届いていましたが、これが以下のように コマンドへの返信 で届くようになります。この返信は /new を実行したユーザにしか見えません。\n通常、返信は ゲーム開始のメッセージの上 にあることになるので、見落とさないように注意です。\nプレイヤごとの色の指定方法の変更 これまで、Discord のユーザとゲーム中のプレイヤの色の紐づけは、Bot からのメッセージへの絵文字でのリアクションで行っていました。これが、メッセージ下のプルダウンからの選択に変更されています。\n従来の手法には、小さくて操作しにくい、色の判別がしにくいなどいくつか課題がありましたが、今回の変更で、簡単かつ確実な操作ができるようになっています。\n従来のコマンドのスラッシュコマンドとの対応付け 基本的には、.au new に相当するものは /new、.au map に相当するものは /map など、従来のコマンドをそのまま / で表記 すれば動作するように対応づけられているものが大半です。\n一方で、一部、パラメータの有無や指定方法が変更されているものもあります。また、キャッシュ制御や全員のアンミュートなど、通常は利用しない一部の特殊な操作が /debug コマンドにまとめられました。使えるコマンドは 公式のヘルプ で一覧されています。\nとはいえ、実際にコマンドを入力しはじめれば使えるコマンドが一覧されますし、指定が必須のものは必ず入力を求められる（入力しないと送信できない）ようになっています。/help を起点にコマンドを見つけて、実際に触ってみるとわかりやすいでしょう。\n使えるコマンドの一覧 今回のアップデートに合わせて、公式のヘルプ が非常に充実しました。全てのコマンドとオプションが列挙されていますので、併せてどうぞ。\nAutoMuteUs - Commands コマンドを丸ごとコピー＆ペーストしたい場合 コピー＆ペーストで使えるようにコマンドの全文を書き下したい場合は、スラッシュコマンドに渡すオプションは \u0026lt;オプション名\u0026gt;:\u0026lt;値\u0026gt; で表現できます。以下は例です。\nAirShip のマップを表示する /map map_name:Airship ユーザ @kurokobo をゲーム内の黒色にリンクさせる /link user:@kurokobo color:black 多言語対応が Crowdin に対応 7.0 でローカライズの仕組みが刷新され、Crowdin に対応しました。刷新直後（7.0.5 まで）は、機械翻訳に置き換えられてしまったことで日本語訳もだいぶ破綻していましたが、すでに複数名にご協力いただけており（ありがとうございます☺️）、7.0.6 以降は落ち着いてきています。\nまだまだ完全ではないので、もし修正の提案などコントリビューションに興味がある方がいたら、Crowdin の AutoMuteUs ワークスペース から参加いただけます。\nその他のノウハウやナレッジ 見かけ上の操作方法は大きく変わりましたが、内部の処理はほとんど変わっていません。設定項目などもほぼ従来のままなので、以下のエントリで紹介している諸々は引き続き有効です。\nAutoMuteUs のよくある質問と回答とかいろいろ Among Us 用ボット AutoMuteUs のあまり知られていない便利な機能 併せてどうぞ。\n公式ボットサービス利用者向けの情報 公式ボットサービスの利用者向けの情報です。\nサーバでスラッシュコマンドが使えない場合 サーバに ボットを招待した時期 によって、/ を入力してもコマンドが表示されない 場合があります。また、コマンドを実行した際に “I\u0026rsquo;m missing the following required permissions to function properly in this server or channel” と怒られる 場合があります。\nこれは、ボットに対してスラッシュコマンドを使う 権限が割り当てられていない ことが原因です。\nこの場合、通常は以下のリンクからボットを サーバに招待しなおす と解消します（キックは不要です）。\nhttps://add.automute.us/ 念のためにキックしてから再招待してもよいですが、ボットをキックすると、.au settings や /settings でカスタマイズしていた設定が初期化される点には注意が必要です。なお、統計情報（戦績）は削除されずに維持されます。\n再招待しても解決しない場合、ボットに対してサーバの中で割り当てているロールのパーミッションと、テキストチャンネルやボイスチャンネルごとのロールに対するパーミッションを確認してください。\nセルフホスト利用者向けの情報 セルフホスト利用者のうち、スラッシュコマンドに対応した 7.0.0 以降を使いたい方向け の情報です。なお、スラッシュコマンドが必要ない場合 は、以前のバージョンを引き続き利用可能 です。\n変更点ざっくりまとめ ボットに必要な権限が変更されました。招待用 URL の再発行とボットの再招待が必要になる場合があります Docker Compose で使う docker-compose.yml と .env が修正されています。最新のファイルをダウンロードして置き換えることをおすすめします ボットの使い道に応じて、.env の中身と起動の仕方に工夫が必要です。若干ややこしい部分です ボットの権限の追加と再招待の必要性 スラッシュコマンドを使うには、サーバごとに従来の bot スコープに加えて application.commands スコープ の許可が必要です。このスコープは比較的最近追加されたもので、詳細な事情は割愛しますが、これまで各所で紹介されている手順で 2021 年 3 月 24 日以降にボットを招待した場合 は、ほとんどの場合スコープが足りない はずです。\nこれに対応するには、以下のドキュメントを参考に 招待用 URL を再発行 し、ボットを 再度サーバに招待 します（キックは不要です）。\ndeploy/BOT_README.md at main · automuteus/deploy 重要なのは、SCOPES の欄で 従来の bot に加えて application.commands にチェック を入れる点です。その下の BOT PERMISSIONS 欄でも、従来の各所の手順のように Administrator ではなく、できればていねいに指定するとよいでしょう。\n併せて、ボットの権限をサーバ内のロールやチャンネルごとのパーミッションで制御している場合は、適宜修正が必要です。\nDocker Compose 用のファイルの更新 スラッシュコマンド化に併せて、以下の Docker Compose 用のファイルが更新されています。\ndocker-compose.yml（ダウンロード用直リンク） sample.env（ダウンロード用直リンク） 7.0.0 以降を利用したい場合は、新しいファイルをダウンロードして、これまで使っていたモノを置き換える（sample.env は .env に名前を変える必要があります）とよいでしょう。具体的な設定値については次項で紹介します。\n環境変数ファイル .env で利用するタグ スラッシュコマンドを利用するには、以下のタグを指定します。\n環境変数 値 AUTOMUTEUS_TAG 7.0.0 以降（7.0.6 以降の最新を推奨） GALACTUS_TAG 3.0.0 以降 最新バージョンは GitHub のリポジトリ（AutoMuteUs、Galactus）で確認できます。基本的には、最新バージョンを使う とよいでしょう。\n古いバージョンも使えますが、その場合でも、7.0.5 まではパーミッション周りのハンドリングや翻訳が微妙な部分があるため、個人的には 7.0.6 以降がおすすめです。\n環境変数ファイル .env の新しいパラメータ sample.env に、ボットが制御するスラッシュコマンドの種類を決める SLASH_COMMAND_GUILD_IDS と、ボット停止時のコマンドの削除を安全に行えるようにする **==STOP_GRACE_PERIOD ==**が追加されました。\nこれの使い方を理解するには、前提として Discord のスラッシュコマンドの仕様と AutoMuteUs の実装をもう一歩踏み込んで理解する必要があります。\n前提： スラッシュコマンドの種類 スラッシュコマンドには、Discord の仕様として グローバルコマンド と ギルドコマンド の二種類が用意されており、おおまかにはそれぞれ次の特性があります。\nグローバルコマンド そのボットを招待している すべてのサーバ でスラッシュコマンドが使えるようになる スラッシュコマンドの登録後、実際にサーバでスラッシュコマンドが使えるようになるまで 最大で 1 時間 かかる ボットがサーバに参加してからの時間ではなく、あくまでコマンドの登録後の時間である点に注意 つまり、コマンドの登録後（≒後述するがボットの起動後）に 1 時間以上が経過していれば、それ以降にそのボットを招待したサーバでは即座にコマンドが使える スラッシュコマンドの削除後、実際にサーバでスラッシュコマンドが見えなくなるまで 最大で 1 時間 かかる ギルドコマンド ボットを招待しているサーバのうち、明示的に指定した特定のサーバでだけ スラッシュコマンドが使えるようになる スラッシュコマンドの登録後、スラッシュコマンドは 直ちに 使えるようになる スラッシュコマンドの削除後、スラッシュコマンドは 直ちに 見えなくなる 前提： AutoMuteUs の実装 AutoMuteUs の現在の実装では、グローバルコマンドもギルドコマンドも、コマンドの登録はボットの起動時に 1 度だけ 行われます。\n同様に、コマンドの削除も停止時に 1 度だけ 行われます。\n新しいパラメータ： SLASH_COMMAND_GUILD_IDS SLASH_COMMAND_GUILD_IDS をデフォルトの 空欄のまま にすると、すべてのスラッシュコマンドは グローバルコマンド として登録されます。\nSLASH_COMMAND_GUILD_IDS にカンマ区切りで サーバ ID を列挙 すると、スラッシュコマンドは 列挙したサーバに対するギルドコマンド として登録されます。\n前述のグローバルコマンドとギルドコマンドの特性を踏まえ、セルフホストしたボットの使い道に応じて設定を決定する必要があります。典型的には、不特定多数から利用されうる公開ボットや常時稼働させるボットであればグローバルコマンドが、利用する規模が小さい場合や頻繁に停止起動する場合はギルドコマンドが、それぞれ使い勝手がよいでしょう。\n新しいパラメータ： STOP_GRACE_PERIOD AutoMuteUs は、停止時（SIGTERM 受信時）に Discord に登録したコマンドを削除しますが、レート制限の影響もあり、通常は完了まで一分程度かかります。一方で Docker は、コンテナの停止処理を開始（SIGTERM を送信）してから、デフォルトでは 10 秒経過するとそのコンテナを強制的に終了（SIGKILL を送信）します。つまり、デフォルトの動作では、AutoMuteUs はコマンドの削除が完了する 前 に強制終了され、Discord 側に情報が残ることになります。これにより、ボットがオフラインであるにも関わらず、中途半端にスラッシュコマンドが引き続き見えてしまう状態が引き起こされます。\nこれを回避するために、停止処理の開始から強制終了に至るまでの待ち時間を指定するパラメータが STOP_GRACE_PERIOD です。\nデフォルトでは 2 分（2m）に設定されていますが、二つ以上のサーバにギルドコマンドとして登録した場合 は、サーバ数に応じて伸ばす とよいでしょう。目安として、1 サーバあたり 1 分、バッファとして固定で 1 分と考えて設定する（例えばサーバが 3 つなら 3 分 + バッファ 1 分で 4m）と安全そうです。\nグローバルコマンドとして登録した場合は、デフォルトの 2 分のままでほとんど安全です（停止後にコマンドが見えなくなるまで最大 1 時間かかりますが、待っていれば消えるはずです）。\nボットの招待と起動の順番 スラッシュコマンドを ギルドコマンド として登録する場合に限り、コマンドの登録がボットの起動時にのみ行われることを踏まえ、ボットを起動する前にボットをサーバに招待しておく必要がある 点に注意が必要です（ボットの招待は、ボットの起動状態とは無関係に行えます）。\nこれは、ボットへの権限の付与がサーバへの招待時に行われることと、ギルドコマンドの登録それ自体に権限が必要なことによる制約です。\nボットの起動時にギルドコマンドの登録に権限不足で失敗すると、以下のようなログとともに起動に失敗し、再起動が繰り返されます。\n... 2022/**/** **:**:** Registering command help in guild 80258********36117 2022/**/** **:**:** Cannot create command: HTTP 403 Forbidden, {\u0026#34;message\u0026#34;: \u0026#34;Missing Access\u0026#34;, \u0026#34;code\u0026#34;: 50001} panic: Cannot create command: HTTP 403 Forbidden, {\u0026#34;message\u0026#34;: \u0026#34;Missing Access\u0026#34;, \u0026#34;code\u0026#34;: 50001} goroutine 1 [running]: log.Panicf(0xd9cf7f, 0x19, 0xc000863dd0, 0x1, 0x1) /usr/local/go/src/log/log.go:358 +0xc5 main.discordMainWrapper(0xc000086390, 0x621b9c2d) /src/main.go:215 +0xfde main.main() /src/main.go:43 +0x76 グローバルコマンド であれば、この制約はありません。\n起動の確認 正しく登録が完了すると、コンテナ automuteus に以下のようなログが記録されます。\n... 2022/**/** **:**:** Registering command new in guild 80258********36117 2022/**/** **:**:** Registering command refresh in guild 80258********36117 2022/**/** **:**:** Registering command pause in guild 80258********36117 2022/**/** **:**:** Registering command end in guild 80258********36117 2022/**/** **:**:** Registering command link in guild 80258********36117 2022/**/** **:**:** Registering command unlink in guild 80258********36117 2022/**/** **:**:** Registering command settings in guild 80258********36117 2022/**/** **:**:** Registering command privacy in guild 80258********36117 2022/**/** **:**:** Registering command info in guild 80258********36117 2022/**/** **:**:** Registering command map in guild 80258********36117 2022/**/** **:**:** Registering command stats in guild 80258********36117 2022/**/** **:**:** Registering command premium in guild 80258********36117 2022/**/** **:**:** Registering command debug in guild 80258********36117 2022/**/** **:**:** Finishing registering all commands! これはギルドコマンドとして登録した場合の例で、グローバルコマンドとして登録した場合は若干表示が異なります（サーバ ID でなく GLOBALLY が表示）が、いずれにしても、最後に Finishing registering all commands! が表示されれば起動は正常に完了 しています。\nスラッシュコマンドが使えない場合 次のような場合は、ボットに必要な権限が足りていない可能性があります。\n/ を入力してもコマンドが表示されない コマンドを実行すると “I\u0026rsquo;m missing the following required permissions to function properly in this server or channel” と怒られる この場合は、前述の ボットの権限の追加と再招待の必要性 を参照して、招待用 URL を再発行 し、ボットを 再度サーバに招待 したり、ロールの設定やチャンネルごとのパーミッション を確認・修正したりしてみてください。\n6.x からのバージョンアップ 前述した Docker Compose 関連のファイルの更新だけ若干要注意ですが、6.x のアップデートのときと同様、統計情報（戦績）などを維持したままアップデートできます。大まかには以下の流れです。\n必要に応じて正しいパーミッションでボットをサーバに再招待する 既存の docker-compose.yml を最新のものに置き換える 既存の .env を最新の sample.env をもとに置き換えて、パラメータを適切に埋める ボットを再起動する（docker-compose down して docker-compose up -d する） 7.x 内でのバージョンアップ いちど 7.x.x の環境を作ったあと、7.x の範囲でバージョンアップをしたい場合は、AUTOMUTEUS_TAG を書き換えて再起動（docker-compose down、docker-compose up -d）するだけで完了します。\nまとめ AutoMuteUs のスラッシュコマンド化と、その周辺のもろもろを紹介しました。\nバグや不明点があれば、サポートの Discord などもうまくつかっていきましょう。\nAutoMuteUs 関連おすすめエントリ AutoMuteUs 7.3 の新機能： データベース内のデータのダウンロード AutoMuteUs 7.0 のリリース： スラッシュコマンド対応などいろいろ AutoMuteUs のよくある質問と回答とかいろいろ Among Us 用ボット AutoMuteUs のあまり知られていない便利な機能 ","date":"2022-03-31T11:22:20Z","image":"/archives/4233/img/image-246.jpg","permalink":"/archives/4233/","title":"AutoMuteUs 7.0 のリリース： スラッシュコマンド対応などいろいろ"},{"content":"背景 oVirt で VM のクローンを作成したいとき、GUI ではメニュから何も気にせず実行できます。\n一方で、これを Ansible から実行しようとしても、oVirt の VM を管理するときに利用する ovirt.ovirt.ovirt_vm モジュール では実現は難しいようです。クローンに類する操作は次の二種のみしか対応していなさげでした。\nテンプレートからの仮想マシンの作成 他の VM のスナップショットからの仮想マシンの作成 実行したいのは、既存の VM からの Ansible を使ったダイレクトなクローン作成です。本エントリでは、これを API を直接叩いてがんばってどうにかする実装例を紹介します。\noVirt 4.4 でテスト済みです。RHV でも動きそうな気はしますが未テストです。AWX での利用を想定していますが、ansible-playbook でも動作します。\n実装例 バリデーションやカスタマイズ性は無視した最小限の実装例です。\nプレイブック 後述の変数を渡して実行すると、クローンの作成がリクエストされます。クローン先の VM は、クローン元の VM とそっくりそのままな構成でできあがります。\nこの実装例では、リクエストが完了した段階でプレイブックが完了します（クローンの作成完了を待ちません）。\n--- - hosts: localhost gather_facts: no tasks: - name: Gather the ID of the source VM ovirt.ovirt.ovirt_vm_info: auth: insecure: yes pattern: \u0026#34;name={{ source_vm }}\u0026#34; register: _source_vm_info - name: Request cloning of the source VM ansible.builtin.uri: url: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;,\u0026#39;OVIRT_URL\u0026#39;) }}/vms/{{ _source_vm_info.ovirt_vms[0].id }}/clone\u0026#34; validate_certs: no url_username: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;,\u0026#39;OVIRT_USERNAME\u0026#39;) }}\u0026#34; url_password: \u0026#34;{{ lookup(\u0026#39;env\u0026#39;,\u0026#39;OVIRT_PASSWORD\u0026#39;) }}\u0026#34; method: POST headers: Version: \u0026#34;4\u0026#34; Content-Type: application/xml Accept: application/xml body: | \u0026lt;action\u0026gt; \u0026lt;vm\u0026gt; \u0026lt;name\u0026gt;{{ target_vm }}\u0026lt;/name\u0026gt; \u0026lt;/vm\u0026gt; \u0026lt;storage_domain\u0026gt; \u0026lt;name\u0026gt;{{ target_storage_domain }}\u0026lt;/name\u0026gt; \u0026lt;/storage_domain\u0026gt; \u0026lt;/action\u0026gt; body_format: raw register: _response - name: Print HTTP response ansible.builtin.debug: msg: \u0026#34;{{ _response.status }}: {{ _response.msg }}\u0026#34; 変数と環境変数 前述のサンプルプレイブックでは、次の三つの 変数 が必要です。AWX を使う場合は Survey などで渡すとよさそうです。\nsource_vm で、クローン元の VM の名前を指定します target_vm で、クローン先の VM の名前を指定します target_storage_domain で、クローン先の VM を保存するストレージドメイン名を指定します また、次の三つの 環境変数 も必要です。AWX から実行する場合は、Red Hat Virtualization タイプの Credential を作成してジョブテンプレートで指定する ことで、これらは自動で渡せます。\nOVIRT_URL で、oVirt Engine の API のベース URL を指定します。通常、https://\u0026lt;oVirt Engine の FQDN\u0026gt;/ovirt-engine/api です OVIRT_USERNAME で、oVirt Engine に認証するユーザ名を指定します OVIRT_PASSWORD で、OVIRT_USERNAME で指定したユーザのパスワードを指定します 考え方 冒頭で紹介した ovirt.ovirt.ovirt_vm モジュール の制約は、このモジュールが利用している Vms サービスの API の仕様 からきているようです。\n一方で、Vms サービスではなく VM ごとの Vm サービス には、clone メソッド（/vms/{vm:id}/clone への POST）が用意されています。これを使えれば、他の VM からのダイレクトなクローンが実現できそうです。\nこれを紐解くと、VM クローン元 VM の ID を URL に含んだエンドポイント に、パラメータとして vm（Vm 型）と storage_domain（StorageDomain 型）を渡せばよさそうで、愚直に cURL で実装するとこうなります。\nurl=\u0026#34;https://\u0026lt;oVirt Engine の FQDN\u0026gt;/ovirt-engine/api\u0026#34; user=\u0026#34;ユーザ名\u0026#34; password=\u0026#39;パスワード\u0026#39; source_vm_id=\u0026#34;クローン元の VM の ID\u0026#34; target_vm_name=\u0026#34;クローン先の VM の名前\u0026#34; target_storage_domain_name=\u0026#34;クローン先のストレージドメインの名前\u0026#34; curl \\ --insecure \\ --user \u0026#34;${user}:${password}\u0026#34; \\ --request POST \\ --header \u0026#34;Version: 4\u0026#34; \\ --header \u0026#34;Content-Type: application/xml\u0026#34; \\ --header \u0026#34;Accept: application/xml\u0026#34; \\ --data \u0026#34; \u0026lt;action\u0026gt; \u0026lt;vm\u0026gt; \u0026lt;name\u0026gt;${target_vm_name}\u0026lt;/name\u0026gt; \u0026lt;/vm\u0026gt; \u0026lt;storage_domain\u0026gt; \u0026lt;name\u0026gt;${target_storage_domain_name}\u0026lt;/name\u0026gt; \u0026lt;/storage_domain\u0026gt; \u0026lt;/action\u0026gt; \u0026#34; \\ \u0026#34;${url}/vms/${source_vm_id}/clone\u0026#34; すなわち、これを Ansible 版 cURL 的な ansible.builtin.uri モジュールで置き換えたのが先のプレイブックです。\n認証情報が必要ですが、AWX で Red Hat Virtualization タイプの Credential を作成してジョブテンプレートに渡せば自動で環境変数として渡されるため、これをそのまま使っています。また、クローン元 VM の ID は、VM の名前で ovirt.ovirt.ovirt_vm_info モジュールで情報を引っ張って抽出しています。\nこの実装例では、API に渡す vm パラメータの中身（XML の \u0026lt;vm\u0026gt;\u0026lt;/vm\u0026gt; の中身）には名前だけを指定しており、クローン先の VM の細かな構成はクローン元の VM のそれに準拠します。クローン先の VM の構成をカスタマイズしたい場合は、XML の中身をいじくりまわしてもどうも効果がない（詳細は未検証……）ようなので、後続に ovirt.ovirt.ovirt_vm モジュールを使うタスクを書いて正攻法で作業するとよさそうです。\nまとめ モジュールが対応していない処理でも、API をがんばって直接たたけばどうにかなる例を紹介しました。\n根本的には上流のモジュールで対応させたい気持ちもありますが、ovirt.ovirt.ovirt_vm モジュールのソースコードは 3,000 行近く あり実装方針を相談しないとツラそうなので、ひとまずはワークアラウンドということで。\n","date":"2022-03-28T14:34:12Z","image":"/archives/4278/img/image-310.png","permalink":"/archives/4278/","title":"oVirt の VM のクローンを Ansible で作成する"},{"content":"はじめに vSphere 7 から、vSphere 環境のライフサイクル管理を担う vLCM が登場し、ESXi のパッケージ構成が ベースイメージ に アドオン や コンポーネント を追加する考え方に変わりました。\nPowerCLI でも、2020 年 4 月にリリースされた 12.0 から、この考え方に基づいてカスタム ISO ファイルの作成が行えるよう、**==New-IsoImage==** など Image Builder 関連の新しいコマンドレットが追加されています。カーネルオプションも含められる ので、慣れるととても便利です。\n使い方は vSphere 7.0 のドキュメント や VCF のドキュメント に充分書いてありますし、リファレンスもあります が、本エントリでは、ドキュメントに書かれていないところを補足しつつ、改めて紹介します。\nなお、実際に使う場合は、バグが修正されている PowerCLI 12.5 以降（VMware.ImageBuilder 7.0.3 以降）を推奨します。また、現状、PowerShell Core（OSS 版の PowerShell、現 PowerShell 7）では動作しない ため、Windows にバンドルされている Windows PowerShell を使う必要 があります。\n作業の大まかな流れ 最初に、イメージの構成を JSON ファイル（仮に spec.json）として定義します。ベースイメージ は必須で、アドオン や コンポーネント は任意です。\nJSON ファイルで 宣言的に記述 できるので、構成管理的な文脈でもうれしいですね。\n{ \u0026#34;base_image\u0026#34;: { \u0026#34;version\u0026#34;: \u0026#34;7.0.3-0.20.19193900\u0026#34; }, \u0026#34;add_on\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;DEL-ESXi-703\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;19193900-A02\u0026#34; }, \u0026#34;components\u0026#34;: { \u0026#34;Net-Community-Driver\u0026#34;: \u0026#34;1.2.2.0-1vmw.700.1.0.15843807\u0026#34; } } あとはこれを New-IsoImage に渡すだけです。JSON ファイルに含めた諸々を取得できるよう、デポ（URL または ZIP ファイル）のパスも併せて渡します。カーネルオプションもここで指定できます。\nNew-IsoImage ` -Depots ` \u0026#34;https://hostupdate.vmware.com/software/VUM/PRODUCTION/main/vmw-depot-index.xml\u0026#34;, ` \u0026#34;.\\DellEMC_Addon_7.0.3_A02.zip\u0026#34;, ` \u0026#34;.\\Net-Community-Driver_1.2.2.0-1vmw.700.1.0.15843807_18835109.zip\u0026#34; ` -SoftwareSpec ` \u0026#34;.\\spec.json\u0026#34; ` -KernelOptions ` \u0026#34;systemMediaSize=min\u0026#34; ` -Destination ` \u0026#34;.\\VMware-VMvisor-Installer-7.0U3c-19193900.x86_64.customized.iso\u0026#34; もうちょっと詳しく 作業の流れをもう少し詳しく紹介します。\nPowerShell 環境の要件 Image Builder 関連のコマンドレットは、VMware.ImageBuilder モジュールで提供されています。PowerCLI をインストールするとこの VMware.ImageBuilder モジュールも一緒にインストールされますが、VMware.ImageBuilder モジュールのみを単体でインストールしても構いません。\nただし、特定条件下で ISO ファイルのビルドに失敗するバグがあるため、PowerCLI 12.5 以降（VMware.ImageBuilder 7.0.3 以降）の利用がおすすめです。\nなお、現状、VMware.ImageBuilder モジュールは PowerShell Core（OSS 版の PowerShell、現 PowerShell 7）では動作しない ため、利用には Windows にバンドルされている Windows PowerShell が必要 です。\n# PowerShell Core では怒られる \u0026gt; Import-Module VMware.ImageBuilder Exception: The VMware.ImageBuilder module is not currently supported on the Core edition of PowerShell. デポの用意 JSON ファイルをつくるにあたって、ベースイメージ や アドオン、コンポーネント を決める必要があります。そして、これらは デポ に含まれています。\nデポ とは、いわゆるいつもの オフラインバンドル（ZIP ファイル）です。VMware Customer Connect からダウンロードできます（ベースイメージは “VMware vSphere Hypervisor (ESXi)” で、アドオンは “OEM Customized Addons” です）。\nあるいは、手元に ZIP ファイルを用意せずとも、次の オンラインデポ も利用できます。\nhttps://hostupdate.vmware.com/software/VUM/PRODUCTION/main/vmw-depot-index.xml https://hostupdate.vmware.com/software/VUM/PRODUCTION/addon-main/vmw-depot-index.xml https://hostupdate.vmware.com/software/VUM/PRODUCTION/iovp-main/vmw-depot-index.xml https://hostupdate.vmware.com/software/VUM/PRODUCTION/vmtools-main/vmw-depot-index.xml 具体的な値の確認 JSON ファイルに含めるベースイメージやアドオン、コンポーネントの具体的な名前やバージョンは、デポの中身を Get-DepotBaseImages、Get-DepotAddons、Get-DepotComponents で確認するとそれぞれ取得できます。\n# ベースイメージの確認（オンラインデポを利用する例） \u0026gt; Get-DepotBaseImages -Depot \u0026#34;https://hostupdate.vmware.com/software/VUM/PRODUCTION/main/vmw-depot-index.xml\u0026#34; Version Vendor Release date ------- ------ ------------ ... 7.0.2-0.25.18538813 VMware, Inc. 09/13/2021 23:00:00 7.0.2-0.30.19290878 VMware, Inc. 02/14/2022 23:00:00 7.0.3-0.20.19193900 VMware, Inc. 01/17/2022 23:00:00 # アドオンの確認（オフラインデポを利用する例） \u0026gt; Get-DepotAddons -Depot \u0026#34;.\\DellEMC_Addon_7.0.3_A02.zip\u0026#34; Name Version ID Vendor Release date ---- ------- -- ------ ------------ DEL-ESXi-703 19193900-A02 DEL-ESXi-703:19193900-A02 Dell Inc. 01/30/2022 23:10:23 # コンポーネントの確認（オフラインデポを利用する例） \u0026gt; Get-DepotComponents -Depot \u0026#34;.\\Net-Community-Driver_1.2.2.0-1vmw.700.1.0.15843807_18835109.zip\u0026#34; Name Version ID Vendor Release date ---- ------- -- ------ ------------ Net-Community-Driver 1.2.2.0-1vmw.700.1.0.15843807 Net-Community-Driver:1.2.2.0-1vmw.700.1.0.15843807 VMware 10/27/2021 06:02:52 JSON ファイルの作成 以下の書式で JSON ファイルを作成します。\n{ \u0026#34;base_image\u0026#34;: { \u0026#34;version\u0026#34;: \u0026#34;\u0026lt;ベースイメージのバージョン\u0026gt;\u0026#34; }, \u0026#34;add_on\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;\u0026lt;アドオンの名前\u0026gt;\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;\u0026lt;アドオンのバージョン\u0026gt;\u0026#34; }, \u0026#34;components\u0026#34;: { \u0026#34;\u0026lt;コンポーネントの名前\u0026gt;\u0026#34;: \u0026#34;\u0026lt;コンポーネントのバージョン\u0026gt;\u0026#34;, \u0026#34;\u0026lt;コンポーネントの名前\u0026gt;\u0026#34;: \u0026#34;\u0026lt;コンポーネントのバージョン\u0026gt;\u0026#34;, \u0026#34;\u0026lt;コンポーネントの名前\u0026gt;\u0026#34;: \u0026#34;\u0026lt;コンポーネントのバージョン\u0026gt;\u0026#34; } } 必須なのはベースイメージだけで、アドオンとコンポーネントは任意で省略できます。例えば、家の Intel NUC 8 用に、Community Networking Driver for ESXi を含めた ESXi 7.0u3 のイメージを作りたい場合は、次のような JSON ファイルを作ります。\n{ \u0026#34;base_image\u0026#34;: { \u0026#34;version\u0026#34;: \u0026#34;7.0.3-0.20.19193900\u0026#34; }, \u0026#34;components\u0026#34;: { \u0026#34;Net-Community-Driver\u0026#34;: \u0026#34;1.2.2.0-1vmw.700.1.0.15843807\u0026#34; } } コマンドの組み立て コマンドのオプションを組み立てます。\n-Depots で JSON ファイルの中身に必要なデポをカンマ区切りで列挙（PowerShell 的な String[]）し、-SoftwareSpec は JSON ファイルを指定します。保存する ISO ファイルは -Destination で指定します。\nNew-IsoImage ` -Depots \u0026#34;\u0026lt;デポ\u0026gt;\u0026#34;, \u0026#34;\u0026lt;デポ\u0026gt;\u0026#34;, \u0026#34;\u0026lt;デポ\u0026gt;\u0026#34; ` -SoftwareSpec \u0026#34;\u0026lt;JSON ファイル\u0026gt;\u0026#34; ` -Destination \u0026#34;\u0026lt;保存する ISO ファイル\u0026gt;\u0026#34; カーネルパラメータを埋め込みたい場合は、-KernelOptions で \u0026lt;オプション\u0026gt;=\u0026lt;値\u0026gt; を指定します。これもカンマ区切りで複数指定できます。\n先の Intel NUC 8 用の JSON ファイルであれば、コマンドは次のようになります。この例では、カーネルパラメータ systemMediaSize=min を追加しています。\nNew-IsoImage ` -Depots ` \u0026#34;https://hostupdate.vmware.com/software/VUM/PRODUCTION/main/vmw-depot-index.xml\u0026#34;, ` \u0026#34;.\\Net-Community-Driver_1.2.2.0-1vmw.700.1.0.15843807_18835109.zip\u0026#34; ` -SoftwareSpec ` \u0026#34;.\\spec.json\u0026#34; ` -KernelOptions ` \u0026#34;systemMediaSize=min\u0026#34; ` -Destination ` \u0026#34;.\\VMware-VMvisor-Installer-7.0U3c-19193900.x86_64.customized.iso\u0026#34; その他、詳しい説明は リファレンス にあります。\n実行後、Could not download and package reserved VIBs など何らかのエラーが出る場合、PowerCLI を 12.5 以上にアップデートすると解消する場合 があります。JSON ファイルやオフラインデポが見つからないエラーで怒られた場合は、指定をフルパスにすると解消するかもしれません。\nvLCM との相互運用性 New-IsoImage で利用する JSON ファイルは、vLCM のそれと互換性があります。手作りした JSON ファイルを vLCM にインポートできますし、逆に vLCM からエクスポートしたファイルをそのまま New-IsoImage で使うことも可能です。\nインポートとエクスポートは、GUI では次の画面です。そもそも ISO ファイルをここで吐かせられもします。ただし、エクスポートが若干バグめいており、正常に動作しない場合は、vCenter Server の SSL 証明書を信頼 して、エクスポート時に :9084 にリダイレクトされたら :443 にする とうまくいくようです。謎です。\nGUI ではなく、次のように PowerCLI でも操作できます。\n# JSON ファイルのエクスポート \u0026gt; Export-LcmClusterDesiredState -Cluster \u0026lt;ホストクラスタ\u0026gt; # JSON ファイルのインポート \u0026gt; Import-LcmClusterDesiredState -Cluster \u0026lt;ホストクラスタ\u0026gt; -LocalSpecLocation \u0026lt;JSON ファイル\u0026gt; エクスポートのコマンドでは、-ExportIsoImage や -ExportOfflineBundle を指定すれば、JSON ファイルの代わりに ISO ファイルやオフラインバンドルも取得できます。\nまとめ vSphere 7 時代の考え方に合わせた、カスタム ISO ファイルの新しい作り方を紹介しました。昔ながらのイメージプロファイルを使った手順（Add-EsxSoftwareDepot して New-EsxImageProfile して Export-EsxImageProfile するアレ）に比べて、宣言的なアプローチになってわかりやすくなっています。\nPowerCLI 12.4 までは、この手法は使うアドオンやコンポーネントによって失敗しがちで動作が安定しない部分がありましたが、12.5 でばっちり安定した感があります。vLCM との親和性も高いので、一貫性のある運用ができてすっきりしますね。\n","date":"2022-02-25T16:07:09Z","image":"/archives/4154/img/image-308.png","permalink":"/archives/4154/","title":"New-IsoImage： PowerCLI でのカスタム ISO ファイルの新しい作り方"},{"content":"Intel の第 12 世代 Core プロセッサが発売されたり、それを積んだ NUC 12 の情報がリークされたりと、すでに次世代の台頭が視野に入りつつある情勢ですが、それを横目に NUC 11 Enthusiast Kit (NUC11PHKi7C, Phantom Canyon) を購入しました。\n2017 年に NUC 6（NUC6i5SYH）を買って、2019 年に NUC 8（NUC8i5BEK）を 2 台買い足しているので、都合 4 台目の NUC です。\n今回購入した NUC11PHKi7C は、NUC にしてはひと回り大きめのお弁当箱サイズです。NUC 6 の Skull Canyon、NUC 8 の Hades Canyon の系譜ですね（今回のコードネームは Phantom Canyon です）。\n大きさの代わりに、GeForce RTX 2060 を積んでいます。もちろん最新のつよつよ GPU には性能は遠く及びませんが、NUC を買う時点でもはや性能以上に小ささを重視してお金を払っているようなものなので、そういうものです。\n今回は、1 TB の NVMe な M.2 SSD と 64 GB の DDR4 メモリを載せました。買うたびに思いますが、昔に比べると安くなりましたね……。\nIntel NUC 11 Enthusiast Kit - NUC11PHKi7C TEAMGROUP ELITE SO-DIMM DDR4 LAPTOP MEMORY TEAMGROUP MP33 M.2 PCIe SSD 試しに FF14 の暁月のフィナーレのベンチマーク を回したところ、1920 x 1080 で 高品質 (デスクトップ PC) の設定で、スコアは 15,000 を超えて『非常に快適』評価になりました。\n2560 x 1440 で 最高品質 にしても 10,000 を超えて『快適』評価です。実際にこの設定でエオルゼアを歩き回っても、平時では 80 fps 程度、人が多いところでも 60 fps 程度は安定して出るので、この小型の筐体でこれだけの操作感が得られるなら充分すぎるでしょう。\n古い NUC は自宅ラボに組み込む予定です。3 台体制になるわけですが、すでにある vSphere 環境にホストを足すよりは新しく oVirt 環境を作るかなあという気持ちです。\n","date":"2021-11-06T09:38:31Z","image":"/archives/4140/img/DSC06793.jpg","permalink":"/archives/4140/","title":"NUC 11 Enthusiast Kit (NUC11PHKi7C) 購入"},{"content":"はじめに 0.13.0 までの AWX Operator は、Kubernetes の クラスタスコープ で動作していました。default ネームスペースに AWX Operator がただ一つ存在していて、同じクラスタ内であればネームスペースを問わず AWX リソースを管理できる状態です。AWX Operator の 0.14.0 では、これが ネームスペーススコープ に変更されています。すなわち、AWX Operator 自身が存在しているネームスペース内でしか AWX リソースを管理できません。\nまた、AWX Operator のデプロイ方法も、GitHub 上のマニフェストファイルを kubectl apply する従来の方法から、0.14.0 では make を使った方法に変更 されています。\n従来、AWX Operator をアップグレードしたい場合（すなわち AWX をアップグレードしたい場合）、AWX リソースのパラメータに互換性がある範囲であれば、単に新しいバージョンのマニフェストファイルを kubectl apply すれば充分でした。しかしながら、前述の変更を踏まると、0.13.0 以前から 0.14.0 以降へアップグレードする際は、不要なリソースの削除など少しだけ追加の手順が必要です。\n前提とする環境 本エントリでは、過去の別エントリ “AWX を AWX Operator でシングルノード K3s にホストする” で紹介している環境を踏まえて、アップグレード前後のバージョンとそれぞれのリソースが動作するネームスペースは、次の状態を前提としています。\n状態 AWX Operator AWX 本体 アップグレード前 0.13.0 @ default 19.3.0 @ awx アップグレード後 0.14.0 @ awx 19.4.0 @ awx アップグレード前は、AWX Operator は default ネームスペースで動作していますが、AWX 本体は awx ネームスペースで動作しています。ここから、アップグレード後にはすべてが awx ネームスペースで動作している状態を目指します。\n手順 大まかには、次の手順で行います。\nバックアップの取得 古い AWX Operator の削除 古い AWX 本体の削除（任意） 新しい AWX Operator のデプロイ 完了の待機 AWX Operator の公式のドキュメントでは、現時点では細かな手順は紹介されていませんが、README.md では少しだけ触れられている ので、そちらも参考にできます。また、本エントリとほぼ同じ内容を ぼくのリポジトリ にも置いています。\nバックアップの取得 何はともあれ、失敗しても大丈夫なように、AWX 本体のバックアップを取得します。以前のエントリ “AWX Operator による AWX のバックアップとリストア” で紹介しています。\n古い AWX Operator の削除 AWX Operator を awx ネームスペースにデプロイする前に、default ネームスペースから削除します。また、AWX Operator の動作するスコープが変わったため、これまでの AWX Operator が利用していたクラスタロールなどが不要になります。併せて削除します。\nkubectl delete deployment awx-operator kubectl delete serviceaccount awx-operator kubectl delete clusterrolebinding awx-operator kubectl delete clusterrole awx-operator 削除したのは AWX Operator だけで、AWX リソースの CRD は削除していないので、この段階ではまだ AWX リソース awx は（awx ネームスペースで）動作しています。\n古い AWX 本体の削除（任意） 後続の手順で新しい AWX Operator をデプロイすると、AWX Operator によって AWX 本体を司るデプロイメントリソースでロールアウトが行われます。この過程では、新しい AWX の Pod ができてから古い AWX の Pod が削除されるため、CPU とメモリの リクエスト値が一時的に Pod ふたつ分消費 されます。\nこのため、Kubernetes クラスタのノードに充分な空きリソースがない場合は、新しい AWX の Pod のスケジュールができずにアップグレードが失敗します。あらかじめ古い AWX 本体を削除しておけば、ロールアウトの過程で確保すべきリクエスト値を Pod ひとつ分に抑制できます。ただし、ロールアウトの履歴は失われます。\nkubectl -n awx delete deployment awx なお、クラスタに充分な空きリソースがある場合 は、この手順は不要 です。\n新しい AWX Operator のデプロイ あとは、新しい AWX Operator をデプロイするだけです。0.14.0 以降の新しいデプロイ手順 に則って、make deploy します。環境変数 NAMWESPACE には、AWX 本体が動作している（いた）ネームスペース名を与えます。\n# リポジトリのクローンと特定のバージョンのチェックアウト git clone https://github.com/ansible/awx-operator.git cd awx-operator git checkout 0.14.0 # AWX Operator が動作する名前空間の指定とデプロイ export NAMESPACE=awx make deploy これにより、awx ネームスペースに AWX Operator がデプロイされます。必要なロールやサービスアカウントも作成され、CRD も併せて更新されます。\n完了の待機 AWX Operator が動作を開始すると、新しい CRD と既存の AWX リソースのパラメータを基に、デプロイメントのロールアウトが自動でトリガされます。これにより、AWX 本体が新しいバージョンにアップグレードされます。\nあとは待つだけです。進捗はログで確認できます。\n$ kubectl -n awx logs -f deployments/awx-operator-controller-manager -c manager ... ----- Ansible Task Status Event StdOut (awx.ansible.com/v1beta1, Kind=AWX, awx/awx) ----- PLAY RECAP ********************************************************************* localhost : ok=56 changed=0 unreachable=0 failed=0 skipped=35 rescued=0 ignored=0 ---------- 完了すると、AWX Operator と AWX 本体の両方のすべてが awx ネームスペースで動作している状態になります。\n$ kubectl -n awx get awx,all,ingress,secrets NAME AGE awx.awx.ansible.com/awx 13m NAME READY STATUS RESTARTS AGE pod/awx-postgres-0 1/1 Running 0 13m pod/awx-operator-controller-manager-68d787cfbd-59wr8 2/2 Running 0 3m42s pod/awx-84d5c45999-xdspl 4/4 Running 0 3m23s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/awx-operator-controller-manager-metrics-service ClusterIP 10.43.81.63 \u0026lt;none\u0026gt; 8443/TCP 3m42s service/awx-postgres ClusterIP None \u0026lt;none\u0026gt; 5432/TCP 13m service/awx-service ClusterIP 10.43.248.150 \u0026lt;none\u0026gt; 80/TCP 13m NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/awx-operator-controller-manager 1/1 1 1 3m42s deployment.apps/awx 1/1 1 1 3m23s NAME DESIRED CURRENT READY AGE replicaset.apps/awx-operator-controller-manager-68d787cfbd 1 1 1 3m42s replicaset.apps/awx-84d5c45999 1 1 1 3m23s NAME READY AGE statefulset.apps/awx-postgres 1/1 13m NAME CLASS HOSTS ADDRESS PORTS AGE ingress.networking.k8s.io/awx-ingress \u0026lt;none\u0026gt; awx.example.com 192.168.0.100 80, 443 13m NAME TYPE DATA AGE secret/default-token-gq4k7 kubernetes.io/service-account-token 3 13m secret/awx-admin-password Opaque 1 13m secret/awx-broadcast-websocket Opaque 1 13m secret/awx-secret-tls kubernetes.io/tls 2 13m secret/awx-postgres-configuration Opaque 6 13m secret/awx-secret-key Opaque 1 13m secret/awx-token-vpc22 kubernetes.io/service-account-token 3 13m secret/awx-operator-controller-manager-token-6m4k9 kubernetes.io/service-account-token 3 3m42s secret/awx-app-credentials Opaque 3 13m 補足 アップグレードとはあまり関係ない部分での補足です。\nAWX の 19.3.0（AWX Operator 0.13.0 に対応）には、LDAP の認証が正常に動作しないバグ（ansible/awx#10883）がありました。このバグは 19.4.0（AWX Operator 0.14.0）で修正されています（19.2.2 以前にはこのバグは含まれません）。 AWX の 19.2.2（AWX Operator 0.12.0）までは、デフォルトで利用される Execution Environment のイメージはバージョンが指定（quay.io/ansible/awx-ee:0.5.0 など）されていましたが、19.3.0 以降では同イメージの latest タグに変更されています。そのまま使うと、Ansible や Collections など中身のバージョンがある日突然変わる事態に遭遇しうるため、バージョンを指定したものか自前の EE に置き換える方が安心して使えそうです。 おわりに AWX Operator の 0.14.0 以降へのアップグレード手順を取り扱いました。\n根本的には AWX Operator は、バージョンが 0.x.x であることからも（セマンティックバージョニング的な意味で）伺える通り、まだまだ開発中で、今後も大幅な変更が入ることは充分に考えられます。この意味では、商用環境で本気で使いたい際は Ansible Automation Controller（旧 Ansible Tower）を選ぶ方が安心感はあるかもしれないですね。\n","date":"2021-10-06T13:37:20Z","image":"/archives/4133/img/image-248.jpg","permalink":"/archives/4133/","title":"AWX Operator の 0.14.0 以降へのアップグレード"},{"content":"はじめに 本エントリでは、AWX のユーザ認証のバックエンドに Active Directory を LDAP サーバとして利用する場合の構成を取り上げます。\n単にログインできるようにするだけではあまり工夫のしどころがないので、もう少し踏み込んだユースケースを想定して、Active Directory 側の グループ と AWX の 組織 や チーム とのマッピングも構成します。\nAWX 側の ロール と組み合わせることで、Active Directory のグループに応じた、いわゆる RBAC を実現できます。\n環境と要件 今回は、ドメイン ansible.local 下に次のような OU（🏢）とグループ（🏠）、ユーザ（🧑）を用意しました。理由は後述しますが、すべてのユーザのプライマリグループはデフォルトの Domain Users で、図示したグループは二つ目以降の追加グループ扱いにしてあります。\n今回は、ざっくり次のような要件の充足を目指します。\nAWX にログインできるメンバを特定の OU に限定する グループによって AWX 側での役割を変える グループによって AWX 側でのチームを変える グループによって AWX 側で実行できるジョブテンプレートを変える つまり、いくつか具体例を示すと、次のような状態です。\nグループ Lab Admins のメンバは AWX のシステム管理者としてふるまえる グループ Auditors のメンバは AWX のシステム監査人としてふるまえる グループ Server Team のメンバは、サーバチームに明示的に許可されたジョブテンプレートしか実行できない。Network Team のメンバも同様にネットワークチームに許可されたものしか触れない OU Hands-On Lab に所属していないユーザはそもそも AWX にログインできない Active Directory 側のグループ構成の注意点 今回の要件のように、Active Directory 側のグループ情報 を利用して AWX 側で何らかの判定や紐づけを行いたい場合、ユーザのプライマリグループの情報は利用できない 点に注意が必要です（Active Directory の仕様に基づく挙動のため、別の LDAP サーバの実装を利用する場合はこの限りではありません）。\n例えば、Active Directory で以下のようなユーザを構成したとします。\nユーザ名 プライマリグループ 追加のグループ hoge-user Domain Users AWX Privileged Users AWX Network Team fuga-user AWX Privileged Users AWX Network Team piyo-user AWX Network Team (なし) これらのユーザを AWX で認証しても、AWX 側では プライマリグループの所属情報は無視 されます。\nつまり、AWX の LDAP の構成で、例えば上記のうち AWX Privileged Users グループの所属ユーザを AWX 側で管理者に 指定した場合でも、実際に管理者になるのは hoge-user だけ で、fuga-user は対象外です。同様に、AWX Network Team に AWX 側で何らかの権限 を与えたとしても、piyo-user は含まれない 状態になります。\n技術的には、これは AWX が使っている Django の django-auth-ldap の仕様と（LDAP サーバとしての）Active Directory の仕様の組み合わせの結果です。詳細は割愛しますが、ざっくりとは、グループの所属を調べる際に django-auth-ldap が グループオブジェクトの member 属性 を利用するのに対し、Active Directory のグループオブジェクトの member 属性には、そのグループをプライマリグループにしているユーザの情報が含まれない ことに起因しています。Active Directory 側では、プライマリグループの所属情報はユーザオブジェクトの primaryGroupID 属性でしか保持されていませんが、django-auth-ldap はそれを参照しないようです。\n標準的な構成では、Active Directory ドメインに作成したユーザのプライマリグループは Domain Users なので、それ以外のグループで制御するように組めば問題にはなりにくいですが、プライマリグループを変更している構成では注意が必要です。\nAWX での LDAP の設定 AWX の LDAP の設定は、Settings \u0026gt; LDAP settings で行います。\nDefault と LDAP1 から LDAP5 までの複数の設定を構成できますが、認証ソースにしたい LDAP サービスがひとつだけの場合は、Default で構成するとよいでしょう。複数設定した場合は、番号順に順次認証が試行されていくようです。\nこのページの設定を説明している公式ドキュメントは以下です。画面が古いままに見えますが、設定項目自体はあまり変わりません。\n20. Setting up LDAP Authentication — Automation Controller Administration Guide v4.0.0 以下、各項目の簡単な説明と設定例です。順番は画面と一部変えています。\nLDAP Server URI / LDAP Start TLS LDAP サーバの URI です。ドキュメントに書かれていませんが、スペースやカンマ区切りで複数のサーバを指定できます。\nldap://dc1.ansible.local, ldap://dc2.ansible.local 暗号化を行う場合は、LDAPS を使うなら ldaps:// に、Start TLS を使うなら LDAP Start TLS を On にします。今回は暗号化していません。\nLDAP Bind DN / LDAP Bind Password LDAP サーバへの問い合わせに利用されるバインドユーザの Distinguished Name（DN）とそのパスワードです。Active Directory では匿名バインドは許可されていないため、指定は必須です。今回は bind ユーザを利用しています。\nCN=bind,CN=Users,DC=ansible,DC=local バインドユーザは、LDAP としてのバインドのためだけに利用するので、後述の LDAP Require Group など AWX 的な要件を満たしたユーザである必要はありません。\nLDAP Group Type Active Directory を利用する場合は、ActiveDirectoryGroupType または NestedActiveDirectoryGroupType を選択します。ユーザのグループの所属状態を検索する際に、グループがネストされた構成を許容 しない 場合は前者、許容 する 場合は後者を指定します。\nこの設定の選択肢は、Django の django-auth-ldap に準拠しています。各タイプの詳細は Django のドキュメント で説明されています。\nActiveDirectoryGroupType NestedActiveDirectoryGroupType 今回はネストされたグループへの所属情報も参照させる必要がある（後述しますが、AWX Users グループのメンバとして他のグループを含めている）ので、後者にしています。\nNestedActiveDirectoryGroupType ドキュメントの記載通り、上記二つのタイプは実際にはそれぞれ MemberDNGroupType(member_attr='member') と NestedMemberDNGroupType(member_attr='member') と同義のようで、必要な引数とその値を後述の LDAP Group Type Parameters で与えさえすればどちらでも機能します。PosixGroupType は POSIX 準拠のオブジェクト（objectClass が posixGroup のグループ）や属性（ユーザの gidNumber やグループの memberUid）を前提に動作するため、それ用に構成していない Active Directory では正しく機能しません。\nLDAP Group Type Parameters 前述の LDAP Group Type で指定したタイプに合わせてパラメータを指定します。このパラメータは、Django の django-auth-ldap のクラスのコンストラクタに渡す引数に対応しています。\n前述の通り、Active Directory を利用する場合は LDAP Group Type は次のどちらかが適切です。\nActiveDirectoryGroupType NestedActiveDirectoryGroupType この二つには、キーとして name_attr の指定が必要です。この値には LDAP オブジェクトの持つ属性（Attributes）のうち、グループ名を保持している属性名を指定します。Active Diretory の場合は cn です。\nしたがって、次の値を設定します。フォーマットは JSON です。\n{ \u0026#34;name_attr\u0026#34;: \u0026#34;cn\u0026#34; } LDAP Require Group / LDAP Deny Group AWX にログイン できる グループ、または できない グループを指定します。\nどちらも空欄にした場合は、Active Directory の全ユーザが AWX にログインできる状態になります。逆に両方を指定した場合は、相補的に機能し、LDAP Require Group のメンバのうち LDAP Deny Group のメンバでないユーザのみがログインできるようになります。\nActive Directory は、通常のシナリオでは AWX 専用とは考えにくく、であれば必ずしも Active Directory の全ユーザが AWX にログインできてよいとは限りません。仕様上は、AWX 側で明示的に何らかの権限を与えない限りは、ログインできるだけでは何の操作権限も持たないので、何も見えないし何も実行できませんが、最小特権の原則に倣えば、ログインすべきユーザだけがログインできる状態が本来です。\nしたがって、ここでは例として、次のグループのメンバ のみ が AWX にログインできるように構成します。\nLab Admins Org Admins Auditors Server Team Network Team ただし、設定欄 LDAP Require Group と LDAP Deny Group は、ひとつ のグループしか設定できません。このため、これらのグループ自体をメンバに持つグループ AWX Users を作成し、それを LDAP Require Group に指定することで、要件を充足させています。\nCN=AWX Users,OU=Management,OU=Hands-On Lab,DC=ansible,DC=local なお、このような ネストされたグループへの所属情報 を認識させるには、前述の LDAP Group Type で接頭辞 Nested を持つタイプを選択する必要があります。\nLDAP User Search ユーザを検索するクエリを指定します。今回は次のように設定しています。フォーマットは JSON です。\n[ \u0026#34;OU=Hands-On Lab,DC=ansible,DC=local\u0026#34;, \u0026#34;SCOPE_SUBTREE\u0026#34;, \u0026#34;(sAMAccountName=%(user)s)\u0026#34; ] これは次の意味です。\nコンテナ OU=Hands-On Lab,DC=ansible,DC=local の 配下のすべて（SCOPE_SUBTREE）のオブジェクトで 属性 sAMAccountName の値が入力されたユーザ名と一致するもの 今回の構成では、冒頭の図の通り AWX にログインしうるユーザが二つの OU に分かれて所属していますが、さらに上位をみれば、すべてのユーザは OU=Hands-On Lab 配下に存在していると言えます。また、OU=Hands-On Lab 配下には、検索範囲に含めるべきでないコンテナは存在していません。よって、検索パスに上位の OU を指定し、そこから再帰検索を意味するスコープ SUBTREE で検索させれば、目的のユーザを確実に見つけられることになります。\n別解として、今回の構成では、次の設定でも結果的には同等に機能します。\n[ [ \u0026#34;OU=Management,OU=Hands-On Lab,DC=ansible,DC=local\u0026#34;, \u0026#34;SCOPE_ONELEVEL\u0026#34;, \u0026#34;(sAMAccountName=%(user)s)\u0026#34; ], [ \u0026#34;OU=Development,OU=Hands-On Lab,DC=ansible,DC=local\u0026#34;, \u0026#34;SCOPE_ONELEVEL\u0026#34;, \u0026#34;(sAMAccountName=%(user)s)\u0026#34; ] ] これは、次の二つの検索クエリを結合させたものです。\nひとつめ コンテナ OU=Management,OU=Hands-On Lab,DC=ansible,DC=local の 直下（SCOPE_ONELEVEL）のオブジェクトで 属性 sAMAccountName の値が入力されたユーザ名と一致するもの ふたつめ コンテナ OU=Development,OU=Hands-On Lab,DC=ansible,DC=local の 直下（SCOPE_ONELEVEL）のオブジェクトで 属性 sAMAccountName の値が入力されたユーザ名と一致するもの ここでは、上位の OU ではなく、下位の OU それぞれを検索しています。すべてのユーザは二つの OU の直下に存在しているため、OU の直下を検索するクエリを二つの OU で実行して結合すれば、目的の範囲の検索が過不足なく行えることになります。\n上位エントリを指定した再帰検索と、下位エントリを指定した検索の結合では、構成次第で検索の範囲や検索のコストも変わってきます。実際の構成に合わせて必要充分な検索ができるクエリにできるとよさそうです。\nLDAP User DN Template ユーザオブジェクトの DN が全ユーザで共通のパタンである場合に、決め打ちで指定するための設定です。プレースホルダ %(user)s がユーザ名に置換されて利用されます。\nLDAP を利用した認証の過程では、認証したいユーザの DN が厳密に特定される必要があります。この目的で、LDAP クライアントは事前に定義した範囲を事前に定義した条件で検索します。この検索の範囲と条件の指定が、前述の LDAP User Search の設定です。\n一方で、認証したいユーザが例外なくすべて特定の OU の直下に存在している場合などの特定の状況下では、ユーザの検索を行わなくとも、ユーザ名から一意の DN を導出できます。このような場合に、ユーザの DN のパタンをあらかじめ指定しておけるのがこの設定です。指定すると、LDAP User Search に基づく検索は行われなくなります。\n今回は、所属する OU によってユーザの DN が異なるため、事前に決め打ちができません。したがって、この設定は利用せず、空欄のままにします。\nLDAP Group Search グループの検索範囲を指定します。入力されたユーザの所属グループ情報は、ここで指定した範囲の中で検索されます。\n今回は次のように指定しています。フォーマットは JSON です。\n[ \u0026#34;OU=Hands-On Lab,DC=ansible,DC=local\u0026#34;, \u0026#34;SCOPE_SUBTREE\u0026#34;, \u0026#34;(objectClass=group)\u0026#34; ] 書式は前述の LDAP User Search と一緒で、\nコンテナ OU=Hands-On Lab,DC=ansible,DC=local の 配下のすべて（SCOPE_SUBTREE）のオブジェクトで 属性 objectClass の値が group のもの を表現しています。Active Directory のグループは、属性 objectClass の値は group です。\nなお、LDAP Group Search には、前述の LDAP User Search とは異なり、複数の検索範囲の結合ができません。このため、すべてのグループが含まれるような（広めの）検索範囲の指定が必要です。\nLDAP User Attribute Map AWX 側でのユーザの属性と LDAP のユーザオブジェクトの属性の対応付けを指定する設定です。\nAWX 側のユーザは、ユーザ名以外に次の属性を持っています。\nメールアドレス（email） 姓（last_name） 名（first_name） LDAP のユーザオブジェクトも、これらに相当する属性を持っています。Active Directory の場合は、上から mail、sn、givenName です。\nしたがって、次のように指定します。フォーマットは JSON です。\n{ \u0026#34;first_name\u0026#34;: \u0026#34;givenName\u0026#34;, \u0026#34;last_name\u0026#34;: \u0026#34;sn\u0026#34;, \u0026#34;email\u0026#34;: \u0026#34;mail\u0026#34; } これにより、Active Directory 側で保持している姓名やメールアドレスの情報が、認証後に AWX 側でも表示されるようになります。\nLDAP User Flags By Group AWX 側で System Administrator または System Auditor として扱う グループ の DN を指定します。\nこの設定は、AWX で新規にユーザを作成する際に指定できる User Type の設定に相当します。前者は全権を持つスーパユーザで、後者は監査ユーザ（すべての情報を閲覧できるが変更や実行はできない権限のユーザ）です。\n指定したグループに所属するユーザは、指定通りのタイプのユーザとしてログインできます。ここでは、Lab Admins グループに所属するユーザを System Administrator に、Auditors グループのユーザを System Auditor に、それぞれ指定しています。フォーマットは JSON です。\n{ \u0026#34;is_superuser\u0026#34;: [ \u0026#34;CN=Lab Admins,OU=Management,OU=Hands-On Lab,DC=ansible,DC=local\u0026#34; ], \u0026#34;is_system_auditor\u0026#34;: [ \u0026#34;CN=Auditors,OU=Management,OU=Hands-On Lab,DC=ansible,DC=local\u0026#34; ] } それぞれリスト型が渡せるため、複数のグループの指定も可能です。いずれの指定にも含まれないグループのユーザは、Normal User 扱いになります。\nLDAP Organization Map AWX 側の 組織（Organization）に、Active Directory のグループのユーザを紐づける設定です。グループのユーザに対して、組織に対する Admin または Member のロールを自動で持たせられます。\n今回は、Active Directory の OU に準じて AWX 側に Management と Development の二つの組織を用意し、それぞれ次のように Admin（admins）と Member（users）を割り当てています。フォーマットは JSON で、最上位のキーが組織名です。\n{ \u0026#34;Management\u0026#34;: { \u0026#34;admins\u0026#34;: [ \u0026#34;CN=Org Admins,OU=Management,OU=Hands-On Lab,DC=ansible,DC=local\u0026#34; ], \u0026#34;remove_admins\u0026#34;: true, \u0026#34;users\u0026#34;: [ \u0026#34;CN=Org Admins,OU=Management,OU=Hands-On Lab,DC=ansible,DC=local\u0026#34; ], \u0026#34;remove_users\u0026#34;: true }, \u0026#34;Development\u0026#34;: { \u0026#34;admins\u0026#34;: [ \u0026#34;CN=Org Admins,OU=Management,OU=Hands-On Lab,DC=ansible,DC=local\u0026#34; ], \u0026#34;remove_admins\u0026#34;: true, \u0026#34;users\u0026#34;: [ \u0026#34;CN=Server Team,OU=Development,OU=Hands-On Lab,DC=ansible,DC=local\u0026#34;, \u0026#34;CN=Network Team,OU=Development,OU=Hands-On Lab,DC=ansible,DC=local\u0026#34; ], \u0026#34;remove_users\u0026#34;: true } } remove_admins と remove_users は、ログイン時点でのグループの所属状態に準じたロールの 剥奪 の実施有無を指定します。例えば、admins に指定したグループのメンバであるユーザが AWX に一度でもログインすると、AWX 側ではそのユーザは組織に対する Admin ロールを持ちますが、同じユーザがグループのメンバではなくなったあとに再度ログインしたとき、remove_admins の設定に応じて以下の挙動を取ります。\nremove_admins が true だと、そのユーザの Admin ロールは剥奪される remove_admins が false だと、そのユーザの Admin ロールは剥奪されない もう一つの remove_users も、Member ロールに対する同様の処理要否の指定です。\n要件次第ではありますが、基本的にはロールの割り当ては Active Directory 側の最新状態に追従する（true にする）ほうが管理上は望ましいでしょう。ただし、グループ外のユーザに手動で割り当てたロールがログイン時に上書きされる可能性もあります。\nなお、users と admins には、グループの DN やそのリストのほかに、true や false、none も指定できます。それぞれ、LDAP でログインした全ユーザにロールを割り当てる設定、割り当てない設定、割り当ても剥奪も何もしない設定です。要件に合わせて remove_* と組み合せるとよさそうです。\nなお、指定した組織が AWX 側に存在しない場合、自動で作成されます。\nLDAP Team Map 前述の LDAP Organization Map と似ていますが、こちらは チーム（Team）に対する所属の指定です。\n最上位のキーをチーム名（ここでは Server Team と Network Team）として、そのチームが所属する組織（organization）、チームのメンバとするグループ（users）、LDAP Organization Map の remove_* と同様のログイン時の所属に応じた自動的な剥奪有無（remove）を設定できます。\n{ \u0026#34;Server Team\u0026#34;: { \u0026#34;organization\u0026#34;: \u0026#34;Development\u0026#34;, \u0026#34;users\u0026#34;: [ \u0026#34;CN=Server Team,OU=Development,OU=Hands-On Lab,DC=ansible,DC=local\u0026#34; ], \u0026#34;remove\u0026#34;: true }, \u0026#34;Network Team\u0026#34;: { \u0026#34;organization\u0026#34;: \u0026#34;Development\u0026#34;, \u0026#34;users\u0026#34;: [ \u0026#34;CN=Network Team,OU=Development,OU=Hands-On Lab,DC=ansible,DC=local\u0026#34; ], \u0026#34;remove\u0026#34;: true } } 組織と同様、指定したチームが AWX 側に存在しない場合、自動で作成されます。\n動作の確認 設定できたら、動きを確認します。\nあらかじめ空のチーム Server Team と Network Team を作成し、各チーム用のジョブテンプレートも作って、チームに Execute ロールを割り当てておきます。ジョブテンプレートへのロールの割り当ては、ジョブテンプレートの Access タブか、チームの Roles タブから行えます。\nSystem Administrator タイプの確認 Lab Admins グループに所属するユーザでログインします。LDAP 設定画面での指定通り、System Administrator タイプのユーザとして振舞えます。\n実際、System Administrator でないとアクセスできない設定画面も操作可能です。\nSystem Auditor タイプの確認 Auditors グループに所属するユーザでログインすると、System Auditor タイプのユーザとして振舞えます。\nSystem Auditor なので、すべての画面にアクセスできますが、閲覧できるだけで、実行や変更はできません。\n組織の Admin ロールの確認 Org Admins グループに所属するユーザでログインすると、ユーザタイプは Normal User ですが、二つの組織 Management と Development の Admin ロールを持っている管理者として振舞えます。今回は（実際ほぼ意味はないですが）Management の Member ロールも与えていたので、そのように表示されています。\n自身が管理する組織に紐づくオブジェクト（インベントリやプロジェクトなど）は追加や変更、削除が可能ですが、自身が属さない組織（例えばデフォルトの Default）のオブジェクトは不可視で、参照すらできません。\n組織の Member ロールの確認 Server Team グループか Network Team グループに所属するユーザでログインすると、Normal User タイプですが、LDAP 設定画面での指定通り、AWX のチームのひとりとしてのロールを持てています。\n今回は事前にチームに対してジョブテンプレートの Execute ロールを与えていたので、このユーザでは所属するチーム用のジョブテンプレートの実行は可能です。それ以外のジョブテンプレートやオブジェクトについては一切の権限を持たないので、何も見えず、何もできません。\nログインが許可されていないユーザの確認 OU Hands-On Lab 配下でないユーザは、ユーザの検索範囲に含まれないので、そもそもログインできません。 OU Hands-On Lab 配下のユーザであっても、AWX Users グループのメンバでない場合はログインは拒否されます。\nその他の確認 LDAP でログインしたユーザは、一覧画面などで LDAP フラグが付与されて区別されます。姓名も確認できます。\nユーザごとのページでは、メールアドレスも連携できていることがわかります。\nトラブルシュート LDAP の認証は、前述の通り Django が司っているため、詳細なログは AWX の Pod のうち awx-web コンテナから確認できます。\nLDAP として行われている検索のクエリと結果も含まれるので、意図した状態にならないときの調査に役立ちます。以下は例えば lab-admin1 でログインしたときのログです。ユーザが検索され、awx users グループと lab admins グループのメンバであること、それ以外のグループのメンバではないことが正しく判定されている様子が見て取れます。\n$ kubectl -n awx logs -f deployment.apps/awx -c awx-web --tail=100 ... django_auth_ldap search_s(\u0026#39;OU=Management,OU=Hands-On Lab,DC=ansible,DC=local\u0026#39;, 1, \u0026#39;(sAMAccountName=%(user)s)\u0026#39;) returned 1 objects: cn=lab-admin1,ou=management,ou=hands-on lab,dc=ansible,dc=local django_auth_ldap search_s(\u0026#39;OU=Development,OU=Hands-On Lab,DC=ansible,DC=local\u0026#39;, 1, \u0026#39;(sAMAccountName=%(user)s)\u0026#39;) returned 0 objects: django_auth_ldap search_s(\u0026#39;OU=Hands-On Lab,DC=ansible,DC=local\u0026#39;, 2, \u0026#39;(\u0026amp;(objectClass=group)(|(member=cn=lab-admin1,ou=management,ou=hands-on lab,dc=ansible,dc=local)))\u0026#39;) returned 1 objects: cn=lab admins,ou=management,ou=hands-on lab,dc=ansible,dc=local django_auth_ldap search_s(\u0026#39;OU=Hands-On Lab,DC=ansible,DC=local\u0026#39;, 2, \u0026#39;(\u0026amp;(objectClass=group)(|(member=cn=lab admins,ou=management,ou=hands-on lab,dc=ansible,dc=local)))\u0026#39;) returned 1 objects: cn=awx users,ou=management,ou=hands-on lab,dc=ansible,dc=local django_auth_ldap search_s(\u0026#39;OU=Hands-On Lab,DC=ansible,DC=local\u0026#39;, 2, \u0026#39;(\u0026amp;(objectClass=group)(|(member=cn=awx users,ou=management,ou=hands-on lab,dc=ansible,dc=local)))\u0026#39;) returned 0 objects: django_auth_ldap cn=lab-admin1,ou=management,ou=hands-on lab,dc=ansible,dc=local is a member of cn=awx users,ou=management,ou=hands-on lab,dc=ansible,dc=local django_auth_ldap Populating Django user lab-admin1 django_auth_ldap cn=lab-admin1,ou=management,ou=hands-on lab,dc=ansible,dc=local is a member of cn=lab admins,ou=management,ou=hands-on lab,dc=ansible,dc=local django_auth_ldap cn=lab-admin1,ou=management,ou=hands-on lab,dc=ansible,dc=local is not a member of cn=auditors,ou=management,ou=hands-on lab,dc=ansible,dc=local django_auth_ldap cn=lab-admin1,ou=management,ou=hands-on lab,dc=ansible,dc=local is not a member of cn=org admins,ou=management,ou=hands-on lab,dc=ansible,dc=local django_auth_ldap cn=lab-admin1,ou=management,ou=hands-on lab,dc=ansible,dc=local is not a member of cn=org admins,ou=management,ou=hands-on lab,dc=ansible,dc=local django_auth_ldap cn=lab-admin1,ou=management,ou=hands-on lab,dc=ansible,dc=local is not a member of cn=org admins,ou=management,ou=hands-on lab,dc=ansible,dc=local ... まとめ AWX の認証を Active Directory で行うための設定と、Active Directory 側のグループの所属に応じて AWX 側での組織やチームの所属を制御する方法を紹介しました。\nLDAP を用いた組織やチームへの紐づけは必須ではなく、例えばとにかく全員ログインさせてしまって後からロールを与えても結果的には同じ状態にはなるわけですが、あらかじめ紐づけを指定しておけば勝手にそのようになるので、当然ながら安全で確実だし、そしてラクです。\n前述の通り、プライマリグループの情報が使えない点には注意が必要ですが、比較的柔軟な紐づけができそうなので、触るヒトがおおい環境では便利に使えそうですね。\n","date":"2021-09-03T16:44:40Z","image":"/archives/4085/img/image-248.jpg","permalink":"/archives/4085/","title":"AWX に Active Directory で認証させて組織やチームと自動で紐づける"},{"content":"community.windows コレクション の現時点で最新の 1.6.0 には、グループやユーザの管理の機能はありますが、それらが所属する Organizational Unit（OU）自体の管理の機能が含まれません。\nAnsible で Active Directory の OU そのものの作成や変更・削除を行うには、PowerShell DSC の ActiveDirectoryDsc モジュール に含まれる ADOrganizationalUnit リソース を ansible.windows.win_dsc モジュールで呼び出すことで実現できます。\n以下、階層化された OU を作るプレイブックの簡単な実装例とその実行例です。\n--- - hosts: dc1.ansible.local gather_facts: false vars: organizational_units: - name: Hands-On Lab path: DC=ansible,DC=local description: Container for Hands-On Lab management # protected: true / false # state: present / absent - name: Management path: OU=Hands-On Lab,DC=ansible,DC=local description: Container for Lab Management - name: Development path: OU=Hands-On Lab,DC=ansible,DC=local description: Container for Lab Users state: present tasks: - name: Ensure required module is installed community.windows.win_psmodule: name: ActiveDirectoryDsc state: present - name: Ensure OUs are exist ansible.windows.win_dsc: resource_name: ADOrganizationalUnit Name: \u0026#34;{{ item.name }}\u0026#34; Path: \u0026#34;{{ item.path }}\u0026#34; Description: \u0026#34;{{ item.description | default(omit) }}\u0026#34; ProtectedFromAccidentalDeletion: \u0026#34;{{ item.protected | default(omit) }}\u0026#34; Ensure: \u0026#34;{{ item.state | default(omit) }}\u0026#34; loop: \u0026#34;{{ organizational_units }}\u0026#34; この例では、あるべき OU の状態は変数で定義して loop で渡しています。また、ActiveDirectoryDsc モジュールはデフォルトでは含まれないので、はじめに community.windows.win_psmodule でインストール済みの状態にさせています。\n$ ansible-runner run . -p ad_manage_ous.yml PLAY [dc1.ansible.local] ******************************************************* TASK [Ensure required module is installed] ************************************* ok: [dc1.ansible.local] TASK [Ensure OUs are exist] **************************************************** changed: [dc1.ansible.local] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Hands-On Lab\u0026#39;, \u0026#39;path\u0026#39;: \u0026#39;DC=ansible,DC=local\u0026#39;, \u0026#39;description\u0026#39;: \u0026#39;Container for Hands-On Lab management\u0026#39;}) changed: [dc1.ansible.local] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Management\u0026#39;, \u0026#39;path\u0026#39;: \u0026#39;OU=Hands-On Lab,DC=ansible,DC=local\u0026#39;, \u0026#39;description\u0026#39;: \u0026#39;Container for Lab Management\u0026#39;}) changed: [dc1.ansible.local] =\u0026gt; (item={\u0026#39;name\u0026#39;: \u0026#39;Development\u0026#39;, \u0026#39;path\u0026#39;: \u0026#39;OU=Hands-On Lab,DC=ansible,DC=local\u0026#39;, \u0026#39;description\u0026#39;: \u0026#39;Container for Lab Users\u0026#39;, \u0026#39;state\u0026#39;: \u0026#39;present\u0026#39;}) PLAY RECAP ********************************************************************* dc1.ansible.local : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 できました。\n削除も Ensure に Absent が渡るようにすれば実行されます。ただし、誤消去防止の保護フラグが立っていても問答無用で消しにかかるようなので、その点は注意が必要そうです。\n利用できるパラメータの詳細は、Get-Help about_ADOrganizationalUnit で確認できます。\n","date":"2021-08-29T06:35:43Z","image":"/archives/4072/img/image-290.png","permalink":"/archives/4072/","title":"Ansible で Active Directory に Organizational Unit（OU）を作る"},{"content":"はじめに 前回 と 前々回 のエントリでは、Ansible AWX 周辺の最近の機能として、Execution Environment や、さらにその周辺の Ansible Runner、Ansible Builder の OSS 版での動きを追いかけました。今回は、Private Automation Hub のアップストリーム版である Ansible Galaxy NG の周辺を見ていきます。\nが、現時点でサポートされている構成はホスト OS への直接のインストールのみで、開発用には Docker Compose ベースの手順もあるものの、いずれにせよ総じて取り回しが少々不自由です。そんなわけで本エントリでは、現時点で気軽に Galaxy NG を試せる方法 として、次の 3 パタンでの実装手順 と、簡単な動作確認 を取り扱います。\nなお、いずれも Galaxy NG のドキュメントには記載がない 方法であり、当然ながら 正式にサポートされる手順ではない 点は注意が必要です。このエントリは 実験レポート程度 に捉え、検証や勉強やテスト など 試用を目的としたユースケースに限定 して遊ぶとよいでしょう。\nDocker で全部入りコンテナを使うパタン おそらくもっとも手軽な Galaxy NG の入手方法 すべてが一つに詰め込まれた既成コンテナイメージを動かすだけ Kubernetes で全部入りコンテナを使うパタン 前述の Docker パタンを愚直に Kubernetes 上に移植したもの プラットフォームを Kubernetes に揃えたいならいちばん気軽 Kubernetes で Pulp Operator を使うパタン おそらく将来的に正式な Kubernetes 上へのデプロイ方法になる気がしているパタン マイクロサービス化されてスケールもできる状態できちんとしたモノができあがる まだまだ開発途上の様子（動かせはする） とはいえ、自製の Collection の表示のテスト など特定用途ではなかなか便利そうです。最初の Docker パタンなら、慣れれば 10 秒で完成 します。\n今回も、必要なファイルは GitHub に置いています。\n前提： Pulp とそのプラグインとしての Galaxy NG Galaxy NG は、それ単体で独立したプロダクトではなく、実際には Pulp と呼ばれる パッケージ配布用プラットフォーム の プラグインのひとつ のようです。\nPulp is a platform for managing repositories of software packages and making them available to a large number of consumers. Pulp can locally mirror all or part of a repository, host your own software packages in repositories, and manage many types of content from multiple sources in one place. Pulp | software repository management\rドキュメントには プラグインの一覧 が掲載されており、例えば次のようなものが提供されていることがわかります。\nAnsible のロールやコレクションを管理するための Ansible プラグイン コンテナイメージを管理するための Container プラグイン Python のパッケージを管理するための Python プラグイン RPM を管理するための RPM プラグイン つまり Galaxy NG は、言い換えるとざっくりとは Ansible プラグインや Container プラグインと連携しながらそれらをまとめて管理するための機能を Pulp に追加するプラグイン、とも言えそうです。たぶん。\n前提： 公式の Galaxy NG の構築手順 公式には、Galaxy NG には現時点で二種類の構築手段がありそうです。\nホスト OS に直接インストールするパタン 標準的なインストール手順は、次のドキュメントで案内されている、ホスト OS に直接 Ansible でインストールするパタンです。今日現在では、これが オンプレミスできちんと使いたい場合に採用できる唯一の手段 でしょう。\nEnd User Installation · ansible/galaxy_ng Wiki 見かけ上はとても簡単そうですが、現段階でこれに従って作業すると、pulp_installer が新しすぎる、pulp-container も新しすぎる、firewalld 用のモジュールが古い、などなどのアレで依存関係の諸々が厄介で、意外と素直には進められません。\nまた、ホスト OS に直接インストールするため、当然ですが /etc や /var にいろいろ置かれたり、systemd にサービスが追加されたり、ファイアウォールルールがいじられたりして、ホスト OS に深く入り込んでしまい、現実的にはそのホストを Galaxy NG 用として専有させる運用になりそうです。\n本気で自前運用をする場合はそれでよいですが、気軽に試したいだけの場合には、フットワークは軽いとはなかなか言い難いですね。\nコンテナイメージをビルドして動作させるパタン（開発用） 開発環境用として、コンテナ下で動作させる方法も用意されています。\nDevelopment Setup · ansible/galaxy_ng Wiki あくまで開発用ではありますが、ホスト OS に直接インストールするよりはだいぶ気軽です。手順に従うと次のようなコンテナ群が動作し、Web UI もコンテナレジストリも利用できる状態ができあがります。\n$ ./compose ps INFO: Using compose profile standalone INFO: galaxy_ng packages installed from source INFO: Image suffix is unset INFO: Volume suffix is unset Name Command State Ports --------------------------------------------------------------------------------------------------------------------- galaxy_ng__base_1 /bin/true Exit 0 galaxy_ng_api_1 /entrypoint.sh run api-reload Up 0.0.0.0:5001-\u0026gt;8000/tcp,:::5001-\u0026gt;8000/tcp galaxy_ng_content-app_1 /entrypoint.sh run content-app Up 0.0.0.0:24816-\u0026gt;24816/tcp,:::24816-\u0026gt;24816/tcp galaxy_ng_postgres_1 docker-entrypoint.sh postgres Up 0.0.0.0:5432-\u0026gt;5432/tcp,:::5432-\u0026gt;5432/tcp galaxy_ng_redis_1 docker-entrypoint.sh redis ... Up 6379/tcp galaxy_ng_resource-manager_1 /entrypoint.sh run resourc ... Up galaxy_ng_ui_1 docker-entrypoint.sh /hub/ ... Up 0.0.0.0:8002-\u0026gt;8002/tcp,:::8002-\u0026gt;8002/tcp galaxy_ng_worker_1 /entrypoint.sh run worker Up コンテナのイメージは自前でビルドが必要で、起動と停止には専用のシェルスクリプトの利用が前提にされてはいますが、クローンしたリポジトリのタグをうまく使えば任意のバージョンの Galaxy NG を実際に動作させられます。試用環境としては充分実用的です。\nDocker で全部入りコンテナを使う ここからは、Galaxy NG のドキュメントでは記載されていない手段での構築を取り扱います。まずは Docker を使って最も手軽に動かすパタンです。\n考え方 前述した通り、Galaxy NG は、実際は Pulp のプラグインのひとつです。であれば、そもそも Pulp 自体をコンテナで動かす手段 があれば、Galaxy NG もコンテナで動かせることが期待できます。\nそんなこんなでドキュメントを確認していくと、Pulp in One Container なるページで、全部入りコンテナ が紹介されています。\nInstalling Pulp 3 and getting all the services running can be challenging. To reduce the complexity of getting started with Pulp, the Pulp team created a single container image that has all necessary services to run Pulp 3. Pulp in One Container | software repository management\rもとになっている Containerfile は、GitHub で管理されているようです。\npulp/pulp-oci-images: Containerfiles and other assets for building Pulp 3 OCI images また、この中に pulp_galaxy_ng ディレクトリ があり、Containerfile を読み解く と、Galaxy NG（現時点では 4.3.1）がバンドルされたイメージであることがわかります。このイメージは CI で Docker Hub にプッシュされているようです。\npulp/pulp-galaxy-ng - Docker Image | Docker Hub というわけで、動かしてみましょう。\n構築手順 とても簡単な 3 ステップです。基本は ドキュメント の通りで、使うイメージの変更と 起動後の手順の追加 だけ配慮します。\nディレクトリを作って、設定ファイルをひとつ作成します。\nmkdir settings pulp_storage pgsql containers cat \u0026lt;\u0026lt;EOF \u0026gt; settings/settings.py CONTENT_ORIGIN=\u0026#39;http://$(hostname):8080\u0026#39; ANSIBLE_API_HOSTNAME=\u0026#39;http://$(hostname):8080\u0026#39; ANSIBLE_CONTENT_HOSTNAME=\u0026#39;http://$(hostname):8080/pulp/content\u0026#39; TOKEN_AUTH_DISABLED=True EOF コンテナを起動させます。\ndocker run --detach \\ --publish 8080:80 \\ --name pulp \\ --volume \u0026#34;$(pwd)/settings\u0026#34;:/etc/pulp \\ --volume \u0026#34;$(pwd)/pulp_storage\u0026#34;:/var/lib/pulp \\ --volume \u0026#34;$(pwd)/pgsql\u0026#34;:/var/lib/pgsql \\ --volume \u0026#34;$(pwd)/containers\u0026#34;:/var/lib/containers \\ --device /dev/fuse \\ pulp/pulp-galaxy-ng:latest あとは、初期設定ファイルを読ませれば完成です。\nDATA_FIXTURE_URL=\u0026#34;https://raw.githubusercontent.com/ansible/galaxy_ng/master/dev/automation-hub/initial_data.json\u0026#34; curl $DATA_FIXTURE_URL | docker exec -i pulp bash -c \u0026#34;cat \u0026gt; /tmp/initial_data.json\u0026#34; docker exec pulp bash -c \u0026#34;/usr/local/bin/pulpcore-manager loaddata /tmp/initial_data.json\u0026#34; これで、http://\u0026lt;IP アドレス\u0026gt;:8080/ から Galaxy NG が使える状態が得られます。初期ユーザは admin で、パスワードも admin です。\n永続化が必要なデータは冒頭で作成したディレクトリ下に置かれるので、停止や起動をしてもデータは消えません。簡単ですね。\nKubernetes で全部入りコンテナを使う 続いて、前述した Docker で全部入りコンテナを使うパタンを、そのまま Kubernetes 上で再現するパタンです。\n考え方 基本的には、Docker での構成を参考に Kubernetes のマニフェスト化するだけです。一点だけ工夫して、ここでは Galayxy NG のエンドポイントを Ingress で HTTPS 化します。\nざっくり構築手順 ボリュームの持たせ方や設定ファイルの読ませ方など、Kubernetes ならではの部分もありますが、愚直にマニフェストを書くだけなので、特に難しいことはありません。ファイルと実際の手順は GitHub に置いて いるので流れだけの紹介にとどめますが、作業は次の通りです。\nIngress 用の自己署名証明書作って マニフェストのホスト名を書き換えて （今回はシングルノード K3s なので）PV の hostPath で使うディレクトリを作って Kustomize で apply する 完成すると、ネームスペース galaxy に前述の全部入りコンテナが動作する Pod ができあがります。\n$ kubectl -n galaxy get all NAME READY STATUS RESTARTS AGE pod/galaxy-78df96fc64-l7tbq 1/1 Running 0 53s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/galaxy-service ClusterIP 10.43.201.53 \u0026lt;none\u0026gt; 80/TCP 6m14s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/galaxy 1/1 1 1 53s NAME DESIRED CURRENT READY AGE replicaset.apps/galaxy-78df96fc64 1 1 1 53s あとは初期設定ファイルを流し込めば完成です。\nPOD_NAME=$(kubectl -n galaxy get pod -l app=galaxy -o name) DATA_FIXTURE_URL=\u0026#34;https://raw.githubusercontent.com/ansible/galaxy_ng/master/dev/automation-hub/initial_data.json\u0026#34; curl $DATA_FIXTURE_URL | kubectl -n galaxy exec -i $POD_NAME -- bash -c \u0026#34;cat \u0026gt; /tmp/initial_data.json\u0026#34; kubectl -n galaxy exec -i $POD_NAME -- bash -c \u0026#34;/usr/local/bin/pulpcore-manager loaddata /tmp/initial_data.json\u0026#34; これで、https://galaxy.example.com/ から Galaxy NG が使える状態が得られます。初期ユーザは admin で、パスワードも admin です。\nKubernetes で Pulp Operator を使う もっと本気で Kubernetes 上で Galaxy NG をホストしたいときの参考実装例です。\n考え方 探すと、Pulp の Operator も存在していました。\npulp/pulp-operator: Kubernetes Operator for Pulp 3. Under active development. Pulp Operator Operator なので、以前のエントリ紹介した AWX の Operator でのデプロイ と似た使い方で、カスタムリソースとして Pulp のインスタンスを定義することになります。\n絶賛開発中のようで、ドキュメントは多くないですが、今回はリポジトリ内のコード群と併せて読み解いていき、次の点あたりを考慮しながらマニフェストを作ってデプロイします。\nIngress で HTTPS 化する Operator のバージョンを、現時点で最新のタグである 0.3.0 に固定する（デフォルトは latest） コンテナレジストリ機能を有効化する ストレージは指定の PV にバインドさせる 各 Pod のレプリカ数は 1 にする ざっくり構築手順 こちらも、 ファイルと実際の手順は GitHub に置いて います。\n手順では、まずは下準備として以下を実施しています。\nIngress 用の自己署名証明書作って マニフェストのホスト名を書き換えて （今回はシングルノード K3s なので）PV の hostPath で使うディレクトリを作る この後、最初に Pulp Operator をまず動作させます。この段階で一緒にカスタムリソース定義（CRD）も読ませています。\nkubectl apply -k galaxy/operator これで、galaxy ネームスペースで Pulp Operator が動作しはじめます。\n$ kubectl -n galaxy get all NAME READY STATUS RESTARTS AGE pod/pulp-operator-75668bb8c-gcj2t 1/1 Running 0 61s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/pulp-operator-metrics ClusterIP 10.43.205.91 \u0026lt;none\u0026gt; 8383/TCP,8686/TCP 55s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/pulp-operator 1/1 1 1 61s NAME DESIRED CURRENT READY AGE replicaset.apps/pulp-operator-75668bb8c 1 1 1 61s 最後に、同じネームスペースに Pulp リソースを定義します。\nkubectl apply -k galaxy/galaxy プレイブックが走りだして、しばらく待つとデプロイが完了します。\n$ kubectl -n galaxy logs -f deployment/pulp-operator ... --------------------------- Ansible Task Status Event StdOut ----------------- PLAY RECAP ********************************************************************* localhost : ok=51 changed=0 unreachable=0 failed=0 skipped=47 rescued=0 ignored=0 ------------------------------------------------------------------------------- できあがりの構成をみると、だいぶ重厚です。\n今回は各 Pod のレプリカ数をすべて 1 に明示的に指定（Pulp リソースの構成を定義した galaxy.yml の中で）していますが、デフォルトが 2 になっているものもあり、本格的かつ大規模な利用を見越した開発が行われていることが伺えます。\n$ kubectl -n galaxy get all,pulp NAME READY STATUS RESTARTS AGE pod/pulp-operator-75668bb8c-kcwzc 1/1 Running 0 3m53s pod/galaxy-postgres-0 1/1 Running 0 3m14s pod/galaxy-redis-6fd7f7dd44-5l7gw 1/1 Running 0 3m10s pod/galaxy-content-77d89f4c46-5f7s7 1/1 Running 0 2m55s pod/galaxy-resource-manager-74895b7b5-hfq6w 1/1 Running 0 2m54s pod/galaxy-worker-7c8ff54785-9twwg 1/1 Running 0 2m53s pod/galaxy-api-7845d86d77-gwt84 1/1 Running 0 2m57s pod/galaxy-web-776cccc64-hxp4f 1/1 Running 2 3m8s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/pulp-operator-metrics ClusterIP 10.43.64.53 \u0026lt;none\u0026gt; 8383/TCP,8686/TCP 3m48s service/galaxy-postgres ClusterIP None \u0026lt;none\u0026gt; 5432/TCP 3m14s service/galaxy-redis ClusterIP 10.43.193.92 \u0026lt;none\u0026gt; 6379/TCP 3m11s service/galaxy-web-svc ClusterIP 10.43.21.92 \u0026lt;none\u0026gt; 24880/TCP 3m7s service/galaxy-api-svc ClusterIP 10.43.148.168 \u0026lt;none\u0026gt; 24817/TCP 2m58s service/galaxy-content-svc ClusterIP 10.43.151.55 \u0026lt;none\u0026gt; 24816/TCP 2m56s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/pulp-operator 1/1 1 1 3m53s deployment.apps/galaxy-redis 1/1 1 1 3m10s deployment.apps/galaxy-content 1/1 1 1 2m55s deployment.apps/galaxy-resource-manager 1/1 1 1 2m54s deployment.apps/galaxy-worker 1/1 1 1 2m53s deployment.apps/galaxy-api 1/1 1 1 2m57s deployment.apps/galaxy-web 1/1 1 1 3m8s NAME DESIRED CURRENT READY AGE replicaset.apps/pulp-operator-75668bb8c 1 1 1 3m53s replicaset.apps/galaxy-redis-6fd7f7dd44 1 1 1 3m10s replicaset.apps/galaxy-content-77d89f4c46 1 1 1 2m55s replicaset.apps/galaxy-resource-manager-74895b7b5 1 1 1 2m54s replicaset.apps/galaxy-worker-7c8ff54785 1 1 1 2m53s replicaset.apps/galaxy-api-7845d86d77 1 1 1 2m57s replicaset.apps/galaxy-web-776cccc64 1 1 1 3m8s NAME READY AGE statefulset.apps/galaxy-postgres 1/1 3m14s NAME AGE pulp.pulp.pulpproject.org/galaxy 3m22s 他の手順と同様に、https://galaxy.example.com/ から Galaxy NG にアクセスできます。\nGalaxy NG の動作確認 できあがった Galaxy NG が正しく Galaxy NG として使えることを、簡単な操作方法の紹介もかねて確認していきます。\nコレクションの同期 公開 Galaxy サーバである https://galaxy.ansible.com/ から、いくつかのコレクションを Galaxy NG に同期させます。\n同期対象を Galaxy の requirements.yml と同じ書式の YAML ファイルで手元で作成します。\n--- collections: - name: community.general source: https://galaxy.ansible.com version: \u0026#34;\u0026gt;=3.2.0\u0026#34; - name: community.kubernetes source: https://galaxy.ansible.com version: \u0026#34;2.0.0\u0026#34; - name: community.vmware source: https://galaxy.ansible.com version: \u0026#34;\u0026gt;=1.10.0,\u0026lt;1.12.0\u0026#34; - name: awx.awx source: https://galaxy.ansible.com version: \u0026#34;\u0026gt;=19.0.0\u0026#34; - name: ansible.utils source: https://galaxy.ansible.com version: \u0026#34;\u0026gt;=2.1.0\u0026#34; この後、Galaxy NG の Collections \u0026gt; Repository Management \u0026gt; Remote で community の Configure から作成した YAML ファイルをアップロードして、Save して Sync すると同期が開始されます。\n同期が完了したら、 Collections \u0026gt; Collections や Collections \u0026gt; Namespaces などで Community リポジトリに切り替えれば、同期されたコレクションの存在が確認できます。\nただし、サムネイルやドキュメントはうまく表示されないようです。\n自製コレクションの登録 ミニマムなコレクションを作って登録してみます。せっかくなので、プラグイン、モジュール、ロールをひとつずつ含めます。\n# 空っぽのガワの作成 ansible-galaxy collection init demo.collection # プラグインの作成 mkdir -p demo/collection/plugins/vars cat \u0026lt;\u0026lt;EOF \u0026gt; demo/collection/plugins/vars/sample_vars.py DOCUMENTATION = \u0026#39;\u0026#39;\u0026#39; --- vars: sample_vars short_description: Add a fixed variable named sample_var version_added: \u0026#34;1.0.0\u0026#34; description: Just add a fixed variable with name sample_var. \u0026#39;\u0026#39;\u0026#39; from ansible.plugins.vars import BaseVarsPlugin class VarsModule(BaseVarsPlugin): def get_vars(self, loader, path, entities): return {\u0026#34;sample_var\u0026#34;: \u0026#34;This is sample variable\u0026#34;} EOF # モジュールの作成 mkdir -p demo/collection/plugins/modules cat \u0026lt;\u0026lt;EOF \u0026gt; demo/collection/plugins/modules/sample_module.py DOCUMENTATION = \u0026#39;\u0026#39;\u0026#39; --- module: sample_module short_description: This is my test module version_added: \u0026#34;1.0.0\u0026#34; description: This is my longer description explaining my test module. \u0026#39;\u0026#39;\u0026#39; from ansible.module_utils.basic import AnsibleModule if __name__ == \u0026#39;__main__\u0026#39;: result = dict(changed=False, message=\u0026#39;Hello from Module\u0026#39;) module = AnsibleModule(argument_spec={}) module.exit_json(**result) EOF # ロールの作成 cd demo/collection/roles ansible-galaxy init sample_role cat \u0026lt;\u0026lt;EOF \u0026gt; sample_role/tasks/main.yml --- - name: Hello debug: msg: \u0026#34;World\u0026#34; EOF # tarball のビルド cd ../ ansible-galaxy collection build これでカレントディレクトリにコレクションの実体である demo-collection-1.0.0.tar.gz ができあがります。\nあとはこれを Galaxy NG に送れば完了です。Galaxy NG 上でコレクションの格納先になる新しいネームスペースを作成（今回は demo）して、GUI またはコマンドでアップロードします。\nGUI からは何も気にせずに Upload collection すればよいですが、コマンドで行う場合は、次の二つの情報を準備しておきます。\n作成したネームスペースの URL を Collections \u0026gt; Namespaces \u0026gt; View collections \u0026gt; CLI Configuration から確認 API のトークンを Collections \u0026gt; API Token から取得 あとは、それらの値を使って ansible-galaxy collection publish コマンドを実行します。サーバ側が自己署名証明書の HTTPS なので -c を付けています。\nansible-galaxy collection publish \\ demo-collection-1.0.0.tar.gz \\ --server https://galaxy.example.com/api/galaxy/content/inbound-demo/ \\ --token d926e******************************3e996 \\ -c アップロードが完了すると、まずは staging リポジトリに登録されます。管理者が Approve しない限り公開されないので、Collections \u0026gt; Approval から承認します（詳細は割愛しますが、デプロイ時に settings.py に galaxy_require_content_approval: \u0026quot;False\u0026quot; が入るように構成すると、承認を不要にできます）。\n承認すると、Collections \u0026gt; Collections の Published リポジトリ内で存在が確認できます。\nDocumentation タブでは、コレクション自体のものだけでなく、ロールやモジュール、プラグインのドキュメントも確認できるようです。\nドキュメント情報は、コレクションとロールは README.md から、モジュールとプラグインはソースコード中の文字列（DOCUMENTATION など）から Ansible 版 pydoc 的なノリで、それぞれ生成されています。細かなドキュメントが同じ画面から見られるようになったのは、現行の Galaxy よりもわかりやすくてよいですね。\nGalaxy NG からのコレクションのインストール この Galaxy NG を ansible.cfg や環境変数で Galaxy サーバとして指定すると、ansible-galaxy コマンドのインストールソースとして利用できます。\n設定ファイルのひな型は Repository Management の画面からコピーできるので、ここを使うのがラクそうです。トークンは API Token から取得できます。\n[galaxy] server_list = published_repo, community_repo [galaxy_server.published_repo] url=https://galaxy.example.com/api/galaxy/content/published/ token=d926e******************************3e996 [galaxy_server.community_repo] url=https://galaxy.example.com/api/galaxy/content/community/ token=d926e******************************3e996 あとは通常通り collection install をするだけです。試しに、前項の手順で公開 Galaxy サーバから同期したコレクションと、自製したコレクションを指定してインストールします。 サーバ側が自己署名証明書の HTTPS なので -c を付けています。\n$ ansible-galaxy collection install community.vmware -c Starting galaxy collection install process Process install dependency map Starting collection install process Downloading https://galaxy.example.com/api/galaxy/v3/artifacts/collections/community/community-vmware-1.11.0.tar.gz to /home/kuro/.ansible/tmp/ansible-local-2778028ny7vtjq5/tmpxhmwondd/community-vmware-1.11.0-__wjvlah Installing \u0026#39;community.vmware:1.11.0\u0026#39; to \u0026#39;/home/kuro/.ansible/collections/ansible_collections/community/vmware\u0026#39; community.vmware:1.11.0 was installed successfully $ ansible-galaxy collection install demo.collection -c Starting galaxy collection install process Process install dependency map Starting collection install process Downloading https://galaxy.example.com/api/galaxy/v3/artifacts/collections/published/demo-collection-1.0.0.tar.gz to /home/kuro/.ansible/tmp/ansible-local-2778121plxlh68p/tmpgqo218ts/demo-collection-1.0.0-z35yilz_ Installing \u0026#39;demo.collection:1.0.0\u0026#39; to \u0026#39;/home/kuro/.ansible/collections/ansible_collections/demo/collection\u0026#39; demo.collection:1.0.0 was installed successfully 出力から、自前の Galaxy NG からダウンロードされていることがわかります。\nさらに、適当なプレイブックを作って動かしてみます。\ncat \u0026lt;\u0026lt;EOF \u0026gt; site.yml - hosts: localhost roles: - demo.collection.sample_role tasks: - demo.collection.sample_module: register: result - debug: var: result EOF ansible-playbook site.yml 自製コレクションのロールとモジュールの正常な動作が確認できました。\n$ ansible-playbook site.yml ... TASK [demo.collection.sample_role : Hello] ****************************************************************** ok: [localhost] =\u0026gt; { \u0026#34;msg\u0026#34;: \u0026#34;World\u0026#34; } TASK [demo.collection.sample_module] ************************************************************************ ... ok: [localhost] =\u0026gt; { \u0026#34;result\u0026#34;: { \u0026#34;changed\u0026#34;: false, \u0026#34;failed\u0026#34;: false, \u0026#34;message\u0026#34;: \u0026#34;Hello from Module\u0026#34; } } コンテナレジストリとしての利用 Galaxy NG は、紹介した手順でデプロイすると、コンテナレジストリとしても使えます。Execution Environment の置き場所として便利そうですね。\nエンドポイントが自己署名証明書を使った HTTPS なので、Insecure Registry としての登録は必要ですが、あとは通常のコンテナレジストリとまったく一緒です。\nsudo tee /etc/docker/daemon.json \u0026lt;\u0026lt;EOF { \u0026#34;insecure-registries\u0026#34; : [\u0026#34;galaxy.example.com\u0026#34;] } EOF sudo systemctl restart docker ここでは、前回のエントリ で使った Execution Environment をプッシュしてみます。タグ付けして、ログインしてプッシュするだけです。\ndocker tag registry.example.com/ansible/ee:2.10-custom galaxy.example.com/demo/ee:2.10-custom docker login galaxy.example.com docker push galaxy.example.com/demo/ee:2.10-custom 結果は Galaxy NG 側の Container Registry で確認できます。\nあまりリッチな見た目ではないですが、README も書けるようです。\nプルも無事にできそうです。\ndocker image rm galaxy.example.com/demo/ee:2.10-custom registry.example.com/ansible/ee:2.10-custom docker pull galaxy.example.com/demo/ee:2.10-custom Galaxy NG を AWX から使う 最後に、実際に AWX と組み合わせて使ってみます。\nGalaxy NG に配置したコレクションを AWX のプロジェクトから使う プロジェクトの collections/requirements.yml にコレクションの情報を記載しておくと、AWX から同期するときにコレクションの取得とインストールも AWX が勝手に行ってくれます。\n公開 Galaxy サーバであれば特に設定なく使えますが、今回の Galaxy NG はプライベートサーバなので、若干の構成が必要です。プロジェクトが利用する Galaxy サーバ は AWX 上の Organization 単位で指定する ため、設定はOrganization を意識しながら行います。\nまずは Galaxy NG の認証情報の登録です。前述した ansible.cfg の中身に相当する情報を、タイプ Ansible Galaxy/Automation Hub API Token の Credential として登録します。この時に、最終的にプロジェクトが紐づく Organization を明示 します。ここでは Default です。\nリポジトリの URL は Galaxy NG の Repository Management の画面から、トークンは API Token からそれぞれ取得できます。\n続けて、Access \u0026gt; Organizations から、==利用する ==Organization の設定画面 を開き、Galaxy Credentials を編集します。ここで、\nその Organization が利用する Galaxy サーバ 複数の Galaxy サーバを利用する場合、その優先順位 を指定します。\n既定で公開 Galaxy サーバが指定済みです。今回はここに先の手順で登録した自前の Galaxy NG の情報を追加し、優先順位をいちばん高くしました。\n最後に、これは今回の環境固有ですが、Galaxy サーバのエンドポイントが自己署名証明書の HTTPS なので、Settings \u0026gt; Jobs \u0026gt; Jobs settings で、Ignore Ansible Galaxy SSL Certificate Verification を On に変更します。\nこれで、AWX から Galaxy NG を利用できる状態が整いました。実験のため、次のコマンドで最小限のファイルを作成し、collection-demo ディレクトリの中身を Git リポジトリにプッシュします。\nmkdir collection-demo cd collection-demo mkdir collections cat \u0026lt;\u0026lt;EOF \u0026gt; collections/requirements.yml --- collections: - name: demo.collection EOF cat \u0026lt;\u0026lt;EOF \u0026gt; site.yml --- - hosts: localhost roles: - demo.collection.sample_role tasks: - demo.collection.sample_module: register: result - debug: var: result EOF これを AWX 側にプロジェクトとして登録して Sync すると、正しく設定できていれば、登録した認証情報を使って、プライベート Galaxy NG からコレクション demo.collection がインストールされます。\nジョブテンプレートを作って実行すると、実際に自製のモジュールが利用できていることがわかります。\nGalaxy NG に配置した Execution Environment を使う AWX 側からは、コンテナレジストリとしての Galaxy NG はもはや何の特別なものではないので、Galaxy NG に配置した Execution Environment を AWX から使うときに必要な操作も、ほかのコンテナレジストリの場合と何ら変わりはありません。\n前回のエントリ と重複しますが、イメージを Kubernetes がプルすることになるので、Galaxy NG を自己署名証明書の HTTPS でホストした場合は、下回りの K3s に対してそこだけ手当をします。\nsudo tee /etc/rancher/k3s/registries.yaml \u0026lt;\u0026lt;EOF configs: galaxy.example.com: tls: insecure_skip_verify: true EOF sudo systemctl restart k3s あとは AWX で Credential を作成してイメージを Execution Environment として登録し、インスタンスレベルかプロジェクトレベルかジョブテンプレートレベルで利用を指定するだけです。具体的な例は 前回のエントリ で取り扱っています。\nトラブルシュート 自前 Galaxy NG を作ったり消したりコレクションを作ったり作り直したり登録したりインストールしたり、などなどがちゃがちゃ繰り返していると、\nansible-galaxy collection install した際 AWX でプロジェクトを同期（Sync した際） に、次のエラーで失敗することがあります。\nERROR! Mismatch artifact hash with downloaded file\nこれは、初回操作時にローカルにキャッシュされたハッシュ値と、実際にダウンロードされたファイルのハッシュ値が一致しないために発生する問題です。キャッシュをクリアすれば、ハッシュ値の情報がサーバから再取得されるため、正常に戻ります。\nローカルでの操作で失敗している場合は、操作しているユーザの ~/.ansible/galaxy_cache/api.json を削除します。\nrm ~/.ansible/galaxy_cache/api.json AWX からの同期に失敗している場合は、AWX のインスタンスの Pod のうち、awx-ee コンテナの ~/.ansible/galaxy_cache/api.json を削除します。ここの ~ の実体は、awx-ee の実行ユーザ runner のホームディレクトリ /home/runner です。\nkubectl -n awx exec -i $(kubectl -n awx get pod -l app.kubernetes.io/name=awx -o name) -c awx-ee -- bash -c \u0026#34;rm ~/.ansible/galaxy_cache/api.json\u0026#34; おわりに 自前で Galaxy NG をホストする比較的手軽な方法として、Docker や Kubernetes を用いた手順をいくつか紹介しました。\n繰り返しにはなりますが、いずれも Galaxy NG のドキュメントには記載がない 方法であり、当然ながら 正式にサポートされる手順ではない 点は注意が必要です。\nしかしながら、Galaxy NG の動きをさくっと確認できる意味では、わりとおもしろいのではないでしょうか。自己責任前提ではありますが、最悪壊れてもどうにかなる気持ちでいられるのであれば、公式の手順での導入をがんばるよりは、だいぶ気軽に便利に使えるようになりそうです。\nあと、本文で取り上げるほどでもない小ネタとして、左ペインのメニュ構成も Galaxy NG の少し前のバージョンとは変わっているようで、例えば My Namespaces は Namespace ページのタブに替わっています。\n","date":"2021-07-25T08:23:23Z","image":"/archives/4008/img/image-271.png","permalink":"/archives/4008/","title":"Ansible Galaxy NG を Docker や Kubernetes で気軽に試す方法いろいろ"},{"content":"はじめに 前回のエントリ “Ansible Runner と Ansible Builder で Execution Environment を作って使う” では、AWX で Execution Environment を使うための前段として、Ansible Runner と Ansible Builder の動作を確認しました。\nこのエントリでは、その続きとして、作成した Execution Environment を実際に AWX から利用する流れを確認します。また、Container Group を作成して、Pod の構成をカスタマイズします。\n前提とする環境 本エントリでは、次の環境を前提に書いています。\nCentOS 8.2 Python 3.9.2 Docker 20.10.7 Kubernetes (K3s) 1.21.2+k3s1 AWX 19.2.2 なお、AWX を K3s 上でホストする方法は、別エントリ “AWX を AWX Operator でシングルノード K3s にホストする” で紹介しています。\nまた、本エントリの再現に必要な諸々の資材は、手順込みで GitHub に配置済み です。\n事前準備 諸々の動作を確かめるため、AWX を触り始める前に、次のモノを用意しました。\nExecution Environment プライベートコンテナレジストリ プライベート Git リポジトリ プレイブック（プライベート Git リポジトリ上） AWX から自前の Execution Environment を使いたい場合は、当然ながら事前に準備が必要です。今回は 前回のエントリ に従って、Ansible Builder で自分でビルドして用意します。なお、後述しますが、そもそも最近の AWX は、デフォルトで Execution Environment として quay.io/ansible/awx-ee が組み込まれている（というか何もしなくても勝手に使われる）ため、自前での用意は必須ではありません。デフォルトの Execution Environment では要件を満たせない（Ansible のバージョンが違う、モジュールが足りないなど）ときにのみ、自前での用意が必要です。\nまた、AWX は、Execution Environment を コンテナレジストリ からプルして利用します。今回のように自分でビルドした Execution Environment を使いたい場合は、つまり、事前にそれをどこかのコンテナレジストリにプッシュしておく必要があります。この目的では、Docker Hub や Quai.io などインタネット上のサービスも利用できますが、インタネット上にコンテナイメージを配置したくない場合を想定し、本エントリでは K3s 上にプライベートコンテナレジストリをデプロイ して利用しています。\nExecution Environment で動作させるプレイブック（≒AWX に追加するプロジェクトの実体）は、ローカルのプロジェクトディレクトリ（実体は Kubernetes の PV）に配置してもよいですが、少し本格的な利用を想定して、SCM から取得させます。この目的で、本エントリでは K3s 上にプライベート Git リポジトリ をデプロイしています。そして、動作させる プレイブック を実際に Git リポジトリ上に用意します。\n上記それぞれのもう一歩踏み込んだ詳細は、本筋ではないのでここでは割愛して、本エントリの末尾で 補足： 事前準備の作業内容 としてまとめています。\nAWX での Execution Environment の利用 事前準備ができて環境が整ったら、AWX で実際に Execution Environment を利用してプレイブックを実行します。\n今回の環境固有の準備 AWX 上で、次の二つの Credential を登録しておきます。\nContainer Registry の Credential（registry.example.com 用） Source Control の Credential（git.example.com 用） また、今回は Git リポジトリが自己署名証明書を利用した HTTPS 接続のため、Settings \u0026gt; Jobs \u0026gt; Jobs settings の Extra Environment Variables で、次の値を設定しておきます。\n{ \u0026#34;GIT_SSL_NO_VERIFY\u0026#34;: \u0026#34;True\u0026#34; } Execution Environment の登録 コンテナレジストリに配置した自前の Execution Environment は、明示的に Execution Environment として AWX に登録が必要です。メニュの Administration \u0026gt; Execution Environment から、名前やイメージのタグ、プルするタイミング、認証情報などを指定して登録します。\n自前の Execution Environment は、今回は事前準備段階で二つ用意してあったので、二つとも登録しました。\nなお、一覧を見ると、デフォルトで AWX EE と Control Plane Execution Environment が登録されていることがわかります。これらは、Execution Environment を明示しなかったジョブや、SCM の Sync ジョブ、管理系のジョブなどで透過的に利用されています。\nプロジェクトの登録 メニュの Resources \u0026gt; Projects から、通常通りにプロジェクトを追加します。ここでは、事前準備で Gitea 上に作成した gitea/ee-demo プロジェクト（中身は GitHub の kurokobo/awx-on-k3s を丸ごとコピーしただけ）を指定しています。\n画面の通り、プロジェクトの登録の段階でも Execution Environment を指定できます。今回は空欄にしましたが、プロジェクトレベルでデフォルトを指定しておき、後続のジョブテンプレートレベルでオーバライドするような使い方も可能です。また、Settings の Miscellaneous System settings から、さらに上位のインスタンスレベルでのデフォルトの Execution Environment も指定できるようです。\n登録したら、Sync しておきます。\nジョブテンプレートの登録 最後に、メニュの Resources \u0026gt; Templates から、通常通りジョブテンプレートを登録します。\n先の手順で登録したプロジェクトを選択して、プレイブックにはテスト用の runner/project/demo.yml を指定しました。これは 前回のエントリ で使ったプレイブックそのもので、自ホスト名や実行ユーザ名、Ansible のバージョン、パッケージやコレクションの一覧を表示してくれるものです。\n画面の通り、ジョブテンプレートでも Execution Environment を指定できます。画面では、自前の Custom EE (2.10) を指定しています。\nここを空欄にすると、インスタンスやプロジェクトのデフォルトが設定されていればそれが、設定されていなければ組み込みの AWX EE 0.5.0 が自動で設定されます。AWX では、プレイブックの実行には何らかの Execution Environment が必須です。\nプレイブックの実行と結果の確認 ジョブテンプレートを保存して実行すると、設定された Execution Environment を利用してプレイブックが実行されます。\n今回の例では、出力からは、Ansible のバージョンが 2.10 で、Ansible Builder でのビルド時に指定した追加パッケージが導入された環境であることが確認できます。意図した通りの環境でプレイブックが実行できたようです。\nジョブテンプレートを修正して、今度は Custom EE (2.9) を指定して再度実行すると、バージョンの変化が見て取れます。\nまた、ジョブテンプレートで Execution Environment を 空 にして保存すると、AWX 19.2.2 では自動で AWX EE 0.5.0 が指定されます。\nそのまま実行すると、結果から、デフォルトの AWX EE 0.5.0 にはあらかじめパブリッククラウド環境などを操作するためのコレクション群やパッケージ群が追加済みであることがわかります。Execution Environment の存在を意識しなくても、ある程度の汎用性がある環境はいつでも使えそうですね。\nKubernetes 上での Execution Environment と Pod の中身 ジョブを実行すると、実行の都度、Execution Environment が Pod として起動し、終了後には削除されます。このため、前回のエントリ でプレイブックを Ansible Runner からコンテナ下で動作させたときと同様、localhost は Pod それ自体 であり、localhost に対する作用は揮発的 で、永続化されません。\nPod の作られっぷり 前回 同様、プレイブックの最後に ansible.builtin.pause を追加して AWX からジョブテンプレートを実行します。\n- name: Simply pause for a while ansible.builtin.pause: minutes: 10 待ち時間中に Kubernetes の Pod の状態を確認すると、AWX が存在するネームスペースに、ジョブ実行用の Pod が作られていることがわかります。次の例では、automation-job-43-77fgq です。\n$ kubectl -n awx get pod NAME READY STATUS RESTARTS AGE awx-postgres-0 1/1 Running 0 2d7h awx-d85c677df-85wvm 4/4 Running 0 2d7h automation-job-43-77fgq 1/1 Running 0 81s 定義を確認すると、コンテナのイメージとして AWX から指定した Execution Environment が利用されていることがわかります。また、Credential で登録した認証情報が imagePullSecrets として渡されていることも確認できます。\n$ kubectl -n awx get pod automation-job-43-77fgq -o json | jq .spec { \u0026#34;containers\u0026#34;: [ { \u0026#34;args\u0026#34;: [ \u0026#34;ansible-runner\u0026#34;, \u0026#34;worker\u0026#34;, \u0026#34;--private-data-dir=/runner\u0026#34; ], \u0026#34;image\u0026#34;: \u0026#34;registry.example.com/ansible/ee:2.10-custom\u0026#34;, ... \u0026#34;volumeMounts\u0026#34;: [ { \u0026#34;mountPath\u0026#34;: \u0026#34;/var/run/secrets/kubernetes.io/serviceaccount\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;kube-api-access-bxqh5\u0026#34;, \u0026#34;readOnly\u0026#34;: true } ] } ], \u0026#34;imagePullSecrets\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;automation-8f2b6-image-pull-secret-4\u0026#34; } ], ... } プレイブックの配置っぷり ところで、前回 の Docker での実行時は、コンテナの /runner にホストのディレクトリが直接マウントされていましたが、Kubernetes 上で動作しているこの Pod には、上記出力例のとおりそのようなマウントの定義はありません。\nしかしながら、Pod の中でコマンドを実行してみると、確かにプレイブックは存在しています。用意したデモプロジェクトのディレクトリ構造のせいでわかりくいですが、Pod の /runner/project 下に、AWX で指定したプロジェクトの中身が配置されています。\n$ kubectl -n awx exec -it automation-job-43-77fgq -- ls -l /runner/project/runner/project total 4 -rw-r--r--. 1 root root 1058 Jul 20 15:07 demo.yml Pod の /runner/project 下のプレイブックは、Docker のときとは異なり、永続ボリュームのマウント ではない 方法で Pod 内に配置されていることになります。\nReceptor と Ansible Runner の Transmit、Worker、Process Pod へのプレイブックの配置とその後の実行は、ざっくり次の二つの仕組みやツールにより実現されているようです。\nAnsible Runner のリモートジョブ実行機能 ansible-runner transmit で、指定されたプレイブックやコマンドライン引数を ZIP で固めて Base64 でエンコードし、標準出力に吐く ansible-runner worker で、transmit から受け取ったデータを展開し、プレイブックを実行して、結果を標準出力に吐く ansible-runner process で、worker から結果を受け取って、コールバック（画面出力やログの保存など）を実行する Receptor 複数のワーカ間でオーバレイネットワークを構成して、データストリームが授受できるようにする Ansible Runner のリモートジョブ実行機能 は、ドキュメントにも例があるとおり、動作は気軽に確認できます。例えば、transmit を実行すると、標準出力に JSON で引数やエンコードされた ZIP ファイルが吐かれる動きが観察できます。\n$ ansible-runner transmit runner -p demo.yml {\u0026#34;kwargs\u0026#34;: {...\u0026#34;playbook\u0026#34;: \u0026#34;demo.yml\u0026#34;, ...\u0026#34;container_image\u0026#34;: \u0026#34;quay.io/ansible/ansible-runner:devel\u0026#34;, ...} {\u0026#34;zipfile\u0026#34;: 2039} UEsDBBQAAAAIAM9V...AE8BAACSBgAAAAA={\u0026#34;eof\u0026#34;: true} さらにこれにパイプで worker を加えると、プレイブックの実行結果の生データ（改行区切りの JSON）が流れてきます。\n$ ansible-runner transmit runner -p demo.yml | ansible-runner worker {\u0026#34;status\u0026#34;: \u0026#34;starting\u0026#34;, ...} {\u0026#34;status\u0026#34;: \u0026#34;running\u0026#34;, ...} ... {..., \u0026#34;stdout\u0026#34;: \u0026#34;\\r\\nTASK [Gathering Facts] *********************************************************\u0026#34;, ...} {..., \u0026#34;stdout\u0026#34;: \u0026#34;\u0026#34;, ...} ... 大事なのは、worker はオリジナルのファイルには触れることなくプレイブックを実行 しているということです。オリジナルのファイルを読んだのは transmit だけ で、worker との間はただの標準入出力のデータのやりとりです。\nさらにこれをパイプして process で終端すると、見慣れたいつもの出力になります。\n$ ansible-runner transmit runner -p demo.yml | ansible-runner worker | ansible-runner process runner ... TASK [Gathering Facts] ********************************************************* ... AWX でのジョブ実行時も、AWX のインスタンス側とジョブを実行する実際の Pod（Execution Environment）側との間で、ansible-runner 同士のプレイブック一式や結果の授受が行われます。つまり大枠は、\nAWX のインスタンス側でオリジナルのプレイブックを取得（Sync）して、ansible-runner transmit（実際はこれに相当する API）を実行（プレイブック一式を ZIP 化してパラメータとともに配信）し 新しく作られたジョブ用の Pod 側で、ansible-runner worker がインスタンス側の transmit からプレイブック一式とパラメータを受け取り、Pod 上に展開して実行し worker の実行結果は AWX のインスタンス側の ansible-runner process（実際は API）が受け取る AWX のインスタンスとジョブ用の Pod の間のデータは、Receptor のネットワークで流す な動きで実現されていることになりそうです。実際、前述の Pod の定義の args からは、ansible-runner にサブコマンド worker が渡されていることが観察できます。Pod で動く worker はオリジナルのファイルを直接読む必要がないので、ボリュームマウントもされていない（実行の都度 transmit から受け取っているだけ）ということですね。\nContainer Group による Pod のカスタマイズ ジョブ用の Pod の定義は、Container Group でカスタマイズできます。Container Group はジョブ用の Pod の動作環境を定義するもので、特に意識しなくてもジョブの実行時にはデフォルトの Container Group である default が透過的に利用されています。\nContainer Group は、ユーザが任意で追加できます。Pod 用のマニフェストを直接編集 できるほか、他の Kubernetes や OpenShift のクラスタの認証情報も渡せます。利用する Container Group は ジョブテンプレート単位で指定 できるので、うまく使うと、\nデータを永続化する connection: local で完結できるプレイブックで、処理したデータを永続ボリューム上に永続化する Pod のスケジューリングを制御する nodeSelector や affinity などを指定して、Pod を特定のノードで起動させる Pod のリソースを制限する resources の limits や requests を指定して、CPU やメモリなどの消費を制限する 別の namespace で動作するようにして、ResourceQuota で制限する Pod の動作をモニタリングする 別の namespace で動作させたり、任意の labels を追加したりして、モニタリングのクエリやフィルタに使う ジョブの処理を別のクラスタにオフロードする ジョブ用の Pod を外部のクラスタに作成させ、処理をオフロードする などなど、いろいろできそうです。\n準備 動作をざっくり確認したいので、まずは準備です。\n今回は、AWX が動作しているネームスペース awx とは別に新しいネームスペース ee-demo を作って、PVC demo-clam も作成しました。PVC には PV demo-volume が割り当てられていて、実体には hostPath で /data/demo を指定しています。\n$ kubectl get ns ee-demo NAME STATUS AGE ee-demo Active 4m19s $ kubectl -n ee-demo get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE demo-claim Bound demo-volume 5Gi RWO demo-volume 4m26s また、この環境がシングルノードなので実際には無意味ですが、nodeSelector の定義が活きることも確認したいので、ノードにラベル awx-node-type=demo も付与しています。\n$ kubectl label nodes kuro-awx01.kuro.lab awx-node-type=demo $ kubectl get nodes --show-labels NAME STATUS ROLES AGE VERSION LABELS kuro-awx01.kuro.lab Ready control-plane,master 3d7h v1.21.2+k3s1 awx-node-type=demo,... ネームスペース周りも試したいので、AWX が Pod の実行時に利用するサービスアカウント awx が、新しいネームスペース ee-demo も触れるようにしました。ここでは、雑な方法ですが、awx 用に定義されているロールとロールバインディングを、ほぼそのまま新しいネームスペース ee-demo 用に流用して作成しています。kubectl get -o json して jq で整形して kubectl create -f です。\n$ kubectl -n awx get role awx -o json | jq \u0026#39;.metadata.namespace=\u0026#34;ee-demo\u0026#34; | del(.metadata.ownerReferences)\u0026#39; | kubectl create -f - $ kubectl -n ee-demo get role NAME CREATED AT awx 2021-07-21T15:59:45Z $ kubectl -n awx get rolebinding awx -o json | jq \u0026#39;.metadata.namespace=\u0026#34;ee-demo\u0026#34; | del(.metadata.ownerReferences) | .subjects[0].namespace=\u0026#34;awx\u0026#34;\u0026#39; | kubectl create -f - $ kubectl -n ee-demo describe rolebinding awx Name: awx Labels: \u0026lt;none\u0026gt; Annotations: \u0026lt;none\u0026gt; Role: Kind: Role Name: awx Subjects: Kind Name Namespace ---- ---- --------- ServiceAccount awx awx Container Group の作成 準備ができたら実践です。Container Group は、メニュの Administration \u0026gt; Instance Group から作成できます。\n作成画面の Customize pod specification にチェックを入れると、Pod のマニフェストを編集できるようになるので、今回は、次の点を構成しました。\nネームスペースを ee-demo に変更 ラベル app: ee-demo-pod を追加 CPU とメモリの requests と limits を追加 ボリュームの /etc/demo へのマウントを追加 動作ノードとして awx-node-type: demo を指定 具体的には、以下のマニフェストを指定しています。\napiVersion: v1 kind: Pod metadata: namespace: ee-demo labels: app: ee-demo-pod spec: containers: - image: \u0026#39;quay.io/ansible/awx-ee:0.5.0\u0026#39; name: worker args: - ansible-runner - worker - \u0026#39;--private-data-dir=/runner\u0026#39; resources: requests: cpu: 500m memory: 100Mi limits: cpu: 1000m memory: 200Mi volumeMounts: - name: demo-volume mountPath: /etc/demo nodeSelector: awx-node-type: demo volumes: - name: demo-volume persistentVolumeClaim: claimName: demo-claim image は変えてもよいですが、プロジェクトやジョブテンプレートの Execution Environment を指定するとオーバライドされるので、ここではデフォルトのままにしています。\nなお、この画面の Credential には、タイプが OpenShift or Kubernetes API Bearer Token の認証情報を追加できます。今回は空欄にしていますが、外部のクラスタにジョブ用の Pod を作成させたい場合は、ここで指定すると機能するようです（試していません）。\nジョブの実行と結果の確認 ジョブテンプレートの Instance Group 欄で、利用する Container Group を指定できます。\n設定を変更してジョブを実行すると、Container Group で定義した通り、ネームスペース ee-demo に Pod が作成されました。\n$ kubectl -n ee-demo get pod NAME READY STATUS RESTARTS AGE automation-job-50-qsjbp 1/1 Running 0 17s 定義を確認すると、Container Group に含めたもろもろがきちんと反映されていることがわかります。\n$ kubectl -n ee-demo get pod automation-job-50-qsjbp -o yaml ... metadata: ... labels: ... app: ee-demo-pod ... spec: containers: ... image: registry.example.com/ansible/ee:2.10-custom ... resources: limits: cpu: \u0026#34;1\u0026#34; memory: 200Mi requests: cpu: 500m memory: 100Mi ... volumeMounts: - mountPath: /etc/demo name: demo-volume ... nodeSelector: awx-node-type: demo ... volumes: - name: demo-volume persistentVolumeClaim: claimName: demo-claim ... Pod の中で /etc/demo 下にファイルを作成させると、実体の PV 内（今回は hostPath の /data/demo）でもファイルが確認できました。ジョブが終了、つまり Pod が停止しても、ファイルが残る状態ができています。\n$ kubectl -n ee-demo exec -it automation-job-50-qsjbp -- touch /etc/demo/test $ ls -l /data/demo total 0 -rw-r--r--. 1 root root 0 Jul 21 12:17 test 楽しいですね。\n補足： 事前準備の作業内容 事前準備として紹介した以下のそれぞれを用意する作業のもうちょっと踏み込んだ紹介です。\nExecution Environment プライベートコンテナレジストリ プライベート Git リポジトリ プレイブック（プライベート Git リポジトリ上） プライベートコンテナレジストリの用意 プライベートコンテナレジストリとして、本来は Private Automation Hub（Galaxy NG）を立てたいところですが、今回はもっと気軽に Docker が提供している registry で代替しています。素の状態では認証なしかつ HTTP での利用になるため、今回は、次の構成にしました。\nホスト名は registry.example.com Kubernetes の Ingress で HTTPS を終端（自己署名証明書を利用） アクセスには htpasswd ベースの認証が必要 ストレージは PVC で永続化 具体的な構築手順は割愛しますが、デプロイに必要なマニフェスト群と具体的な手順は、GitHub に配置 しています。\nなお、紹介した通り、Execution Environment は、AWX 経由で実行すると Kubernetes の Pod として実行されます。つまり、コンテナレジストリに実際にアクセスしてイメージをプルするのは Kubernetes です。\nしたがって、イメージのプルに認証が必要な場合は、その認証を Kubernetes 側で行えるようにする必要があるわけですが、AWX 側でレジストリの Credential を作って Execution Environment の設定画面で指定しておくと、あとは勝手にやってくれます。具体的には、その Credential をもとに AWX によって Kubernetes 側（の Execution Environment が起動するネームスペース）に .docerconfigjson を含むシークレットが作成され、Pod に imagePullSecrets として追加されます。\nただし、レジストリのエンドポイントが自己署名証明書の HTTPS の場合は、SSL の検証を無効にする必要があり、この目的で、前述の手順の末尾では、/etc/rancher/k3s/registries.yaml を編集しています。\n最終的には以下の状態になっています。\n$ sudo /usr/local/bin/crictl info | jq .config.registry.configs { \u0026#34;registry.example.com\u0026#34;: { \u0026#34;tls\u0026#34;: { \u0026#34;insecure_skip_verify\u0026#34;: true, ... 自製 Execution Environment の用意 コンテナレジストリが用意できて動作が確認できたら、利用したい Execution Environment をビルドしてプッシュします。具体的な手順は 前回のエントリ と完全に重複するので、まるっと割愛します。必要な資材と手順は GitHub に配置 しています。\n今回は、ベースイメージとして quay.io/ansible/ansible-runner の stable-2.10-devel と stable-2.9-devel を利用し、次の二つの自製 Execution Environment をビルドしてコンテナレジストリに配置しました。\n$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE registry.example.com/ansible/ee 2.10-custom 6fb343319a80 24 hours ago 740MB registry.example.com/ansible/ee 2.9-custom 050cf7076379 25 hours ago 871MB $ reg tags -k registry.example.com/ansible/ee 2.10-custom 2.9-custom reg コマンドは、コンテナレジストリのコマンドラインクライアント です。前述の コンテナレジストリの構築手順中 に登場しています。今回利用したコンテナレジストリ registry は Web GUI や CLI がないため、プッシュされたことの確認にこの reg を使っています。気軽に使えて便利です。\nプライベート Git リポジトリの用意 AWX で SCM をプロジェクトのソースにできるように、Git リポジトリを用意します。GitHub や Backlog などのプライベートリポジトリでももちろん構いませんが、今回は、コンテナレジストリ同様、インタネット上に配置したくない場合を想定して、プライベート Git リポジトリとして Gitea を K3s 上に用意しています。必要な資材と手順は GitHub に配置 しています。\n今回は、次の構成で用意しました。\nホスト名は git.example.com Kubernetes の Ingress で HTTPS を終端（自己署名証明書を利用） ストレージは PVC で永続化 RDBMS は SQLite3 用意が面倒であれば、さんざん紹介している GitHub のリポジトリ kurokobo/awx-on-k3s をそのまま SCM として突っ込んでも大丈夫です。\nプレイブックの用意 最後に、AWX から Execution Environment で動かしたいプレイブックを用意します。\nこの目的では、前回のエントリ で Ansible Runner の動作確認に使ったプレイブックが流用できるので、今回はこのエントリで使っている GitHub のリポジトリ kurokobo/awx-on-k3s の中身を丸ごとそのまま、Gitea 上に作成した gitea/ee-demo リポジトリにプッシュしました。\nこれも用意が面倒であれば、さんざん紹介している GitHub のリポジトリ kurokobo/awx-on-k3s をそのまま使っても大丈夫です。\nPod からの名前解決の確認 DNS が構成済み（この例では上流で *.example.com を解決できる）の場合は特に気にしなくてよいですが、/etc/hosts でがんばっている場合は、AWX、つまり K3s の Pod の中 から K3s ホストの /etc/hosts を使った名前解決 ができる必要があります。例えば、AWX に SCM として git.example.com のリポジトリを追加しても、名前解決ができなければ当然 Sync ができません。\nこの対策の簡単な実装例として、GitHub に dnsmasq と K3s 用の resolv.conf を使った設定例 を載せています。最終的には、次のように Pod の中から *.example.com の名前解決ができる必要があります。\n$ cat /etc/hosts ... 192.168.0.100 awx.example.com 192.168.0.100 registry.example.com 192.168.0.100 git.example.com $ kubectl run -it --rm --restart=Never busybox --image=busybox:1.28 -- nslookup git.example.com Server: 10.43.0.10 Address 1: 10.43.0.10 kube-dns.kube-system.svc.cluster.local Name: git.example.com Address 1: 192.168.0.100 pod \u0026#34;busybox\u0026#34; deleted おわりに AWX 上で自前の Execution Environment が利用できる状態を構成して、実際に動きを確認しました。また、ジョブを実行する Pod を Container Group でカスタマイズできることも併せて確認しました。\nAWX の画面からは、かつて存在していた Python の仮想環境に関する設定が消え、Execution Environment に置き換えられている ことも見て取れます。大きなアーキテクチャの変更であり、ユースケースによっては従来通りの使い方ができなくて困る場合も出てくるかもしれませんが、どちらかといえば新しいアーキテクチャに合わせて使い方自体を変えていく方が理想なのだろうとは思います。\n個人的には、Kubernetes 上での動作も含め、Ansible のコンテナ化はとてもうれしい流れです。もりもり使っていきたいですね。\n","date":"2021-07-21T17:38:15Z","image":"/archives/3955/img/image-248.jpg","permalink":"/archives/3955/","title":"Ansible AWX で Execution Environment と Container Group を作って使う"},{"content":"はじめに Ansible Automation Platform 2.0 がアーリーアクセスで提供されはじめ、次期メジャリリースの情報が出てきました。\nAnsible Automation Platform 2.0 Early Access Homepage - Red Hat Customer Portal Introducing Ansible Automation Platform 2.0 Early Access - Red Hat Customer Portal What’s New in Ansible Automation Controller 4.0 - Red Hat Customer Portal What’s New in Private Automation Hub - Red Hat Customer Portal 目立つところでは、Ansible Tower が Ansible Automation Controller に改名されていますが、アーキテクチャ面でも、制御プレーンと実行プレーンを疎結合にするために Execution Environment（EE）の概念が新たに登場しています。\n従来、プレイブックに応じて Python のモジュールや Collection を使い分けたい場合、典型的には Python の仮想環境を用いた環境の分離を行っていました。Execution Environment（EE）は、平たくいえばこれを コンテナに置き換えるもの であり、Ansible のランタイムをコンテナ化したもの と言えそうです。必ずしも Tower（AWX）と組み合わせなくても使えますが、Tower（AWX）目線でも、本体のインスタンスと実行環境が分離されるので、スケールもしやすくなりそうです。\n本エントリでは、AWX での Execution Environment（EE）の動きを確かめるための準備として、Ansible Runner と Ansible Builder の動作を確認します。次のエントリ では、本エントリで作成した自前の Execution Environment（EE）を実際に AWX から利用します。\n前提とする環境 本エントリでは、次の環境を前提に書いています。\nCentOS 8.2 Python 3.9.2 Docker 20.10.7 ざっくりアーキテクチャ Execution Environment の周辺には、Ansible Runner と Ansible Builder が登場します。荒く整理すると、たぶんこんな感じです。\nExecution Environment Ansible のランタイムを含んだ、Red Hat UBI（OSS 版は CentOS）ベースの コンテナイメージ 追加したい Collection などの情報をテキストファイル群で定義して Ansible Builder でビルドすることで、独自の環境を作成できる コンテナレジストリで Execution Environment を公開することで、他の環境で容易に再利用でき、環境間での動作の一貫性が得られるほか、スケールもしやすくなる Ansible Builder Execution Environment をビルドするツール 雑に言うと、設定ファイル を基に Dockerfile を自動生成 して podman build（docker build）を実行してくれるツール ベースイメージを指定すると、Ansible のバージョンを（ある程度）選べる 任意の Galaxy の Collection や Role、Python モジュール（pip）、OS パッケージ（RPM）を含められる ビルド中に任意の処理を追加できる（Dockerfile に RUN を追記できるイメージ） Ansible Runner プレイブックを Execution Environment 上で実行するためのツール Execution Environment を使わずに ansible-playbook のラッパとしても利用できる ビルドした Execution Environment はいわゆるコンテナイメージなので、任意のコンテナレジストリにプッシュできます。Ansible はコンテナの中で動くことになるため、従来のような Python の仮想環境の考慮が不要になるほか、環境の移植性（動作の一貫性）も高められ、またスケールもしやすくなります。\nなお、コンテナイメージとしての Execution Environment を管理する目的で、Private Automation Hub がコンテナレジストリとしての役割も担えるようになるようですが、本エントリでは割愛します。\nAnsible Builder を使う だいたいの情報は 公式のドキュメント にまとまっています。\nansible/ansible-builder Introduction — ansible-builder documentation 本エントリで使っているファイル群の実物は、GitHub に配置済み です。\nインストール ansible-builder だけあればよく、ansible はなくても動きます。\npython3 -m pip install ansible-builder 必要なファイルの用意 ディレクトリをひとつ作って、execution-environment.yml としてメインの構成定義ファイルを作成します。\nmkdir builder cd builder cat \u0026lt;\u0026lt;EOF \u0026gt; execution-environment.yml --- version: 1 build_arg_defaults: EE_BASE_IMAGE: quay.io/ansible/ansible-runner:stable-2.10-devel ansible_config: ansible.cfg dependencies: galaxy: requirements.yml python: requirements.txt system: bindep.txt additional_build_steps: prepend: - RUN whoami - RUN cat /etc/os-release append: - RUN echo This is a post-install command! - RUN ls -la /etc EOF ベースイメージは、無指定の場合はデフォルトで quay.io/ansible/ansible-runner:latest になるようで、現時点では latest は stable-2.11-devel と同義です。選択肢は quay.io/ansible/ansible-runner のタグ一覧ページ で確認できますが、2.9 や 2.10 も用意されていて、上記例では違いを分かりやすくするため 2.10 を指定しています。利用する Ansible のバージョンを変更したい場合は、ベースイメージを差し替えるのが無難そうですね。\nほか、ansible.cfg や requirements.yml、requirements.txt、bindep.txt は必要に応じて指定します。これらは各ツールにそのまま渡されるため、中の書式はそれぞれの標準に従います。ここでは省略しますが、実際の例を GitHub に配置済み です。\nadditional_build_steps では、コンテナイメージのビルドプロセス中に任意の処理を追加できます。具体的には、生成される Dockerfile の所定の位置（pip install と dnf install の前後）に任意の行を追記できます。追記される位置は実際に生成された Dockerfile（後述）の中身を見るのが確実です。\nカスタマイズ済みの設定ファイルの例として、AWX に組み込まれている Execution Environment のソースである ansible/awx-ee も参考にできます。レイヤが重なりすぎるうえに副作用が予想できないのでおすすめはしませんが、自前でビルドするためのベースイメージとしても ビルド済みのイメージ quay.io/ansible/awx-ee も利用できそうでした。\nExecution Environment のビルド 必要なファイルが用意できたら、ansible-builder build でビルドできます。引数で、できあがったイメージに付与するタグと、ビルドに利用するコンテナランタイム（デフォルトは podman で、今回は docker）を指定します。--verbosity（-v）は任意ですが、無いとビルドの過程が表示されないようです。\n$ ansible-builder build --tag registry.example.com/ansible/ee:2.10-custom --container-runtime docker --verbosity 3 Ansible Builder is building your execution environment image, \u0026#34;registry.example.com/ansible/ee:2.10-custom\u0026#34;. File context/_build/requirements.yml will be created. File context/_build/requirements.txt will be created. File context/_build/bindep.txt will be created. File context/_build/ansible.cfg will be created. Rewriting Containerfile to capture collection requirements Running command: docker build -f context/Dockerfile -t registry.example.com/ansible/ee:2.10-custom context Sending build context to Docker daemon 7.68kB Step 1/25 : ARG EE_BASE_IMAGE=quay.io/ansible/ansible-runner:stable-2.10-devel Step 2/25 : ARG EE_BUILDER_IMAGE=quay.io/ansible/ansible-builder:latest Step 3/25 : FROM $EE_BASE_IMAGE as galaxy ... Removing intermediate container a083001a665a ---\u0026gt; 050cf7076379 Successfully built 050cf7076379 Successfully tagged registry.example.com/ansible/ee:2.10-custom Complete! The build context can be found at: /home/********/awx-on-k3s/builder/context できた Execution Environment は通常のコンテナイメージなので、docker image ls で確認できます。\n$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE registry.example.com/ansible/ee 2.10-custom 6fb343319a80 2 minutes ago 871MB このあと手作業で Ansible Runner から使うだけであれば、Execution Environment はここまでで完成です。\nもしこの Execution Environment を AWX や外部のシステムから利用する要件がある場合は、コンテナレジストリにプッシュが必要です。今回は GitHub に配置済みのファイル を使って K3s 上にプライベートコンテナレジストリを作成しているので、このままプッシュしました。\n$ docker push registry.example.com/ansible/ee:2.10-custom The push refers to repository [registry.example.com/ansible/ee] ... 2.10-custom: digest: sha256:0138445c58253c733f2e255b618469d9f61337901c13e3be6412984fd835ad55 size: 3880 Dockerfile の確認 ビルドの過程で生成された Dockerfile は、カレントディレクトリの context ディレクトリに保存されています。additional_build_steps で指定したコマンドの追加されっぷりなどは、実物を見た方が確実です。\n$ cat context/Dockerfile ARG EE_BASE_IMAGE=quay.io/ansible/ansible-runner:stable-2.10-devel ARG EE_BUILDER_IMAGE=quay.io/ansible/ansible-builder:latest FROM $EE_BASE_IMAGE as galaxy ARG ANSIBLE_GALAXY_CLI_COLLECTION_OPTS= USER root ... なお、イメージのビルドは行わずに Dockerfile の生成だけを行いたい場合は、ansible-builder create コマンドが利用できます。\n$ ansible-builder create --verbosity 3 Ansible Builder is generating your execution environment build context. File context/_build/requirements.yml will be created. File context/_build/requirements.txt will be created. File context/_build/bindep.txt will be created. File context/_build/ansible.cfg will be created. Rewriting Containerfile to capture collection requirements Complete! The build context can be found at: /home/********/awx-on-k3s/builder/context Ansible Runner を使う これもだいたいの情報は 公式のドキュメント にまとまっています。\nansible/ansible-runner Ansible Runner — ansible-runner documentation 本エントリで使っているファイル群の実物は、GitHub に配置済み です。\nインストール Execution Environment を利用する（≒ Ansible をコンテナで動かす）だけであれば、ansible-runner だけあればよく、ansible はなくても動きます。ansible-playbook コマンドのラッパとして利用する場合は、当然ながら ansible も必要です。\npython3 -m pip install ansible-runner プレイブックの用意 ディレクトリをひとつ作って、必要なファイルを配置していきます。実物は GitHub に配置済み ですが、ここではサンプルとして Ansible の実行環境の構成が判断しやすいプレイブックにしています。\nプレイブックは project ディレクトリに配置します。\nmkdir -p runner/project cat \u0026lt;\u0026lt;EOF \u0026gt; runner/project/demo.yml --- - hosts: localhost connection: local tasks: - name: Ensure that the host is reachable ansible.builtin.ping: - name: Print variables for debugging ansible.builtin.debug: var: data vars: data: ansible_playbook_python: \u0026#34;{{ ansible_playbook_python }}\u0026#34; ansible_python_version: \u0026#34;{{ ansible_python_version }}\u0026#34; ansible_python.executable: \u0026#34;{{ ansible_python.executable }}\u0026#34; ansible_version.full: \u0026#34;{{ ansible_version.full }}\u0026#34; - name: Invoke debug commands ansible.builtin.command: \u0026#34;{{ item }}\u0026#34; changed_when: false failed_when: false loop: - hostname - whoami - pwd - python3 -m pip list - ansible-galaxy collection list -p . register: command_results - ansible.builtin.debug: msg: \u0026#34;{{ item.stdout_lines }}\u0026#34; loop: \u0026#34;{{ command_results.results }}\u0026#34; loop_control: label: \u0026#34;{{ item.cmd }}\u0026#34; EOF ansible-playbook のラッパとして使う Ansible Runner は、ローカルの ansible-playbook のラッパとしても利用できます。素の状態（引数で設定を与えず、設定ファイルも用意しなかった場合）ではこの動作です。この場合、Execution Environment の概念はまったく登場しません。\n$ ansible-runner run runner -p demo.yml [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match \u0026#39;all\u0026#39; PLAY [localhost] ************************************************************** ... PLAY RECAP ********************************************************************* localhost : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 このパタンでは、ansible-playbook コマンドを手打ちする状態とほとんど変わらず、localhost は ansible-runner を実行したホストそれ自体 を指します。つまり、従来通りの意味の localhost で、何ら特別なことはなく、出力された hostname、whoami、pip list、ansible-galaxy collection list などの実行結果は、見慣れた自ホストのものになるはずです。サンプルには含めていませんが、localhost に対する ansible.builtin.yum や ansible.builtin.copy なども、意図通りに自ホストに対して 実行されます。\nなお、実行結果のログ や 実行されたコマンド は、自動で artifacts ディレクトリに保存 されます。何もせずとも相当詳細に残るので、これまで ansible.cfg などでいろいろしないといけなかったログ取得はあまり気にしなくてもよくなりそうです。ただし、何もしないと自動で削除はされずに増え続けるので、適宜 --rotate-artifacts 10 など保持世代数を指定して掃除する必要があります。\nプレイブックを Execution Environment で実行する ansible-runner に --process-isolation を引数で与えるか、設定ファイル env/settings で process_isolation を true にして、--process-isolation-executable（process_isolation_executable）で docker（または podman）を指定すると、Execution Environment を使ってコンテナ上でプレイブックを実行できます。\nmkdir runner/env cat \u0026lt;\u0026lt;EOF \u0026gt; runner/env/settings --- process_isolation: true process_isolation_executable: docker container_image: registry.example.com/ansible/ee:2.10-custom EOF なお、--process-isolation（process_isolation）を有効にして --process-isolation-executable（process_isolation_executable）を指定しないと、デフォルトでは bwrap でのサンドボックス化が行われますが、個人的に bwrap になじみがないので割愛しています。\nExecution Environment にはデフォルトで quay.io/ansible/ansible-runner:devel が利用されますが、引数 --container-image や設定 container_image で任意のものを指定できます。ここでは先の Ansible Builder の手順で自製した registry.example.com/ansible/ee:2.10-custom の利用を指定しています。\n設定ファイルを用意したうえで ansible-runner を実行すると、指定した Execution Environment を使ってプレイブックが実行されます。この場合も 実行結果のログ や 実行されたコマンド は、自動で artifacts ディレクトリに保存 されます。\n$ ansible-runner run runner -p demo.yml ... PLAY [localhost] *************************************************************** ... ok: [localhost] =\u0026gt; { \u0026#34;data\u0026#34;: { ... \u0026#34;ansible_version.full\u0026#34;: \u0026#34;2.10.12rc1.post0\u0026#34; } } ok: [localhost] =\u0026gt; (item=[\u0026#39;python3\u0026#39;, \u0026#39;-m\u0026#39;, \u0026#39;pip\u0026#39;, \u0026#39;list\u0026#39;]) =\u0026gt; { \u0026#34;msg\u0026#34;: [ \u0026#34;Package Version\u0026#34;, \u0026#34;-------------------- ----------------\u0026#34;, ... \u0026#34;example-pypi-package 0.1.0\u0026#34;, ... ok: [localhost] =\u0026gt; (item=[\u0026#39;ansible-galaxy\u0026#39;, \u0026#39;collection\u0026#39;, \u0026#39;list\u0026#39;, \u0026#39;-p\u0026#39;, \u0026#39;.\u0026#39;]) =\u0026gt; { \u0026#34;msg\u0026#34;: [ \u0026#34;\u0026#34;, \u0026#34;# /usr/share/ansible/collections/ansible_collections\u0026#34;, \u0026#34;Collection Version\u0026#34;, \u0026#34;----------------- -------\u0026#34;, \u0026#34;community.general 3.3.2 \u0026#34; ] } PLAY RECAP ********************************************************************* localhost : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 出力からは、以下のような動作が確認できるはずです。\nAnsible のバージョンが、ビルド時に指定したベースイメージ（今回は 2.10 系）のものになっていること pip list や ansible-galaxy collection list の結果に、ビルド時に指定したモノが含まれること Execution Environment の切り替えは設定ファイルやコマンドライン引数で簡単に行えるため、紹介した例以外でもいろいろ試すと、指定した Execution Environment に応じた Ansible のバージョンやパッケージの有無の変化が確認できます。自製した Execution Environment だけでなく、今回のサンプルのように標準モジュール群だけで動くシンプルなプレイブックであれば、 ビルド用のベースイメージである quay.io/ansible/ansible-runner もそのまま Execution Environment として利用できるので、実際に試してみると楽しいですね。より汎用性の高い既成 Execution Environment としては、AWX に組み込まれている quay.io/ansible/awx-ee（パブリッククラウド関係のコレクションなどが導入済み）も利用できます。\nコンテナの作られっぷりと connection: local の扱い ところで、Execution Environment を使った場合、==localhost== は、自ホストではなくコンテナ自体を指す ことに注意が必要です。実際、プレイブックの出力結果から、ホスト名（hostname）や実行ユーザ（whoami）が見慣れないものに替わっていることが確認できるはずです。\n上記の例では、Ansible は Docker のコンテナとして動作します。確認のため、プレイブックに ansible.builtin.pause を追加し、再度同様に ansible-runner を実行します（余談ですが、ansible.builtin.pause を使った入力は Ansible Runner 経由ではうまく動かないようです……）。\n- name: Simply pause for a while ansible.builtin.pause: minutes: 10 待ち時間中に、実際のコンテナの存在が確認できます。\n$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f0a7a89e5b28 registry.example.com/ansible/ee:2.10-custom \u0026#34;entrypoint ansible-…\u0026#34; 11 seconds ago Up 11 seconds ansible_runner_9bbedba8-520a-4392-9db9-16f76aa597de このコンテナを inspect すると、ansible-runner の実行時に指定したディレクトリが /runner にマウントされていることがわかります。\n$ docker inspect ansible_runner_9bbedba8-520a-4392-9db9-16f76aa597de | jq .[0].Mounts [ { \u0026#34;Type\u0026#34;: \u0026#34;bind\u0026#34;, \u0026#34;Source\u0026#34;: \u0026#34;/home/********/awx-on-k3s/runner\u0026#34;, \u0026#34;Destination\u0026#34;: \u0026#34;/runner\u0026#34;, \u0026#34;Mode\u0026#34;: \u0026#34;Z\u0026#34;, \u0026#34;RW\u0026#34;: true, \u0026#34;Propagation\u0026#34;: \u0026#34;rprivate\u0026#34; }, { \u0026#34;Type\u0026#34;: \u0026#34;bind\u0026#34;, \u0026#34;Source\u0026#34;: \u0026#34;/etc/sample\u0026#34;, \u0026#34;Destination\u0026#34;: \u0026#34;/etc/sample\u0026#34;, \u0026#34;Mode\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;RW\u0026#34;: true, \u0026#34;Propagation\u0026#34;: \u0026#34;rprivate\u0026#34; } ] 逆にいえば、コンテナとして動作しているので、マウントされた /runner 以外 の ホスト側のファイルや状態は Ansible からは不可視 です。\nこのため、connection: local で localhost に作用するプレイブック は、/runner 下以外は コンテナに閉じた揮発的な作用 にしかならず、意図した結果が得られない 点には注意が必要です。\nこの点は GitHub の Issue でも課題提起がされていて、今後動作が変わる可能性がありますが、現時点では、従来の意味での localhost に作用させたい場合、コンテナの中から自ホストに SSH させる 必要があります。\nなお、次の設定を加えると、コンテナへのバインドマウントを追加でき、/runner 以外のパスも Ansible から作用させられるようになりますが、この設定はドキュメントに記載がない ため、将来的な取り扱いは不明です。\ncontainer_volume_mounts: - /etc/sample:/etc/sample 逆に、ドキュメントには process_isolation_show_paths の記載がありますが、ソースコードを見た範囲では、bwrap 専用のようで、Docker や Podman では使えませんでした。\nおわりに AWX で EE を使うために、前段として Ansible Builder と Ansible Runner の動作を確認しました。\nこれまでコンテナでの動作は積極的にはサポートされていませんでしたが、大手を振って使えるようになるわけで、たいへんうれしい進化です。環境の切り替えが用意になっただけでなく、複数の環境での状態の再現が容易になり、可搬性や動作の一貫性も保証しやすくなりました。これからは Playbook は生で叩かずに、できるだけ EE で叩いていきたいですね。\n次のエントリ では、実際に AWX で EE を利用します。\n","date":"2021-07-18T16:32:51Z","image":"/archives/3915/img/image-248.jpg","permalink":"/archives/3915/","title":"Ansible Runner と Ansible Builder で Execution Environment を作って使う"},{"content":"AutoMuteUs のサポート Discord や DM で聴かれたこと、日本語での情報が少ないことなどをまとめています。思いつきでほどほどに更新します。\nDocker や Docker Compose それ自体の使い方というよりは、維持や運用の仕方とかそっちの方面が中心です。\nその他の Tips 系は別エントリ Among Us 用ボット AutoMuteUs のあまり知られていない便利な機能 でも紹介しています。\n全利用者向けの情報 公式ボットサービスやセルフホストなどの利用形態に関わらず広く適用できる情報です。\nスラッシュコマンドについて知りたい バージョン 7.0 で、操作方法がスラッシュコマンドに変更されました。別エントリで詳しく紹介しています。\nAutoMuteUs 7.0 リリース： スラッシュコマンド対応などいろいろ | kurokobo.com 使えるスラッシュコマンドは、公式のヘルプで一覧されています。\nAutoMuteUs - Commands 15 人ロビーや新色に対応したい 現在の AutoMuteUs は、2021 年 6 月に追加された 15 人ロビーや新色にも対応済み ですが、利用形態に応じて以下の作業が必要です。\nAutoMuteUs 公式ボットサービス を利用している場合\nキャプチャソフト（AmongUsCapture）を 4.1.1 以降に更新します セルフホスト している場合\nキャプチャソフト（AmongUsCapture）を 4.1.1 以降に更新します ボット本体 を次のいずれかに更新します 公式 の Docker 版 の 6.15.1 以降 に更新します 非公式の Windows 版（後述）に更新します セルフホストしている場合、特に新色の絵文字が表示されない例や、旧 Windows 版（2.4.3）からの乗り換え先が判然としない例が散見されます。\nこれらについては、後述の 新色の絵文字が表示されない や 旧 Windows 版（2.4.3）で 15 人ロビーや新色を使いたい でも触れているので、併せてどうぞ。\nAmong Us のアップデートが配信された後に AutoMuteUs が動かない Among Us のアップデートが配信された場合、しばらくは ほぼ確実に AutoMuteUs は動かなくなります。\n実装として、AutoMuteUs が動くためには、\nキャプチャソフト（AmongUsCapture）本体 キャプチャソフト（AmongUsCapture）が使う オフセットファイル ボット本体 の 三つすべて が正常に動作する必要があり、そしてこのうち オフセットファイル は、アップデートがどれだけ軽微であってもほぼ確実に人力で修正が必要 です。\nこのため、Among Us にアップデートがあれば、人力での修正対応が完了するまで AutoMuteUs は動かなくなります。アップデートの内容次第では、さらにキャプチャソフト本体やボット本体に修正が必要になることもあり、この場合はさらに時間が必要です。\n現状の体制では、いずれも 開発陣のできるひとができるときにやる のが精々で、要するにベストエフォートなので、ぼくももりもりお手伝いはしているものの、対応完了予定日などは案内できません。\nAutoMuteUs 関連の更新状況が知りたい 非公式 に Twitter でこんなボットを回しています。\nAutoMuteUs 更新自動お知らせボット @amu_info_bot 固定ツイートにある通り、\nAmong Us のアップデート配信 Steam、Epic Games、Microsoft Store 版 AutoMuteUs のアップデート ボットサービスのバージョンアップ セルフホスト用 Docker 版の新バージョンリリース AmongUsCapture のアップデート AmongUsCapture 本体の新バージョンリリース AmongUsCapture 用のオフセットファイルのリリース を、全自動でお知らせしています。たまにアップデートを誤検知して誤情報を流してしまっていますが、おおむねうまくお知らせできていますので、ご活用ください。\nMod に対応してほしい 公式には予定はなさそうで、最近の Issue #423 で Mod のサポートについての方針がコメントされています。\n非公式には、TownOfUs に対応させたキャプチャソフト が有志の手で開発されていましたが、それ以外は現状見当たらず、動くか試してみてダメだったら諦める、もしくは自分で作る、くらいしかなさそうです。\nキャプチャソフトがクラッシュして起動できない キャプチャソフトを起動しても即座にクラッシュしてしまう場合は、保存されている設定やキャッシュをリセットすると改善する場合があります。\nエクスプローラで %AppData% を開き、AmongUsCapture フォルダを削除して、再度キャプチャを起動してください。解決しない場合は AutoMuteUs のサポート Discord へどうぞ。\n人数が多いときのミュートされるまでのラグを解消したい AutoMuteUs では、ミュートとアンミュートに Discord の API を利用しています。Discord の API には一定時間内に許容される操作の回数の制限（Rate Limits）があり、前述のラグはこの制限によるものです。\nこれには、利用形態に応じて、次の対策が有効です。詳細は別エントリ Among Us 用ボット AutoMuteUs のあまり知られていない便利な機能 で紹介しています。\n利用形態 高速化方法 公式サービス（無償またはブロンズ） キャプチャボットによる高速化 公式サービス（シルバー以上） キャプチャボットによる高速化 ワーカボットによる高速化 セルフホスト キャプチャボットによる高速化（5.0.0 以上のみ） ワーカボットによる高速化 Steam 版以外の利用可否が知りたい 現状、キャプチャソフトは Steam 版、Epic Games 版、Microsoft Store 版の三種についてアップデートに追従する対応を行っています。\nAutoMuteUs の動作テストをしたい AutoMuteUs の動作をテストしたい場合、もっとも確実な方法は、実際に 4 人以上のプレイヤでゲームを遊んでみる ことです（ひとりで端末を 4 台並べるのも手です）。\nが、ひとが集められない、まずはざっくり確認したい、などの場合は、次の手順で最低限の動作を確認できます。\nAutoMuteUs でゲームを開始してキャプチャソフトとリンクする ✅ ボットがコマンドに応答し、メッセージを投稿できることを確認 ✅ キャプチャとリンクできることを確認 Among Us でゲームをホストし、ロビーに入る ✅ ボットからのメッセージが LOBBY 状態になっていることを確認 ✅ Discord のメッセージ上でプレイヤの色が正しく表示されていることを確認 ✅ リージョンやマップ、ロビーコードが正しいことを確認 Discord 上の絵文字でリアクションし、Discord アカウントとリンクさせる ✅ ボットがリアクションに応答することを確認 Discord 上で自分を Server Mute と Server Deafen にする ボイスチャネルで自分を右クリックしてチェックを入れる 赤いミュートアイコンと赤いスピーカミュートアイコンが自分に付く コマンド /debug unmute-all（.au unmuteall）を実行する ✅ 自分のミュートとスピーカミュートが解除されることを確認 とくに手順 5 では、通常ではテストしにくいミュート関連の権限有無も確認できます。\nゲーム開始後にボットから送られてくる URL がクリックできない /new でゲームを開始すると、キャプチャソフトとリンクするための情報がボットから送られてきますが、このメッセージ中の aucapture:// から始まる文字列が、2023 年の春頃からクリックできなくなっていました。これは Discord 側の仕様変更によるものです。\n現在では、代わりに本文中の “このリンクをクリックする”（英語の場合は “click here”）がクリックできるようになっており、ここからリンクできます。あるいは、以下でも対応可能です。\naucapture:// から始まる文字列をコピーし、ブラウザのアドレスバーにペーストして開く キャプチャソフトの歯車の右隣のアイコン（Open the manual connection window）から手動でリンクする セルフホストの場合は、8.2.0 で以降を使い、環境変数 API_SERVER_URL に automuteus コンテナの API_PORT（デフォルトは 80）を向く URL を指定することで、本文中のリンクが機能するようになります。実装面は 別エントリの API の項目 で紹介しています。\nその他の便利な機能が知りたい 併せてどうぞ（宣伝）。\nAmong Us 用ボット AutoMuteUs のあまり知られていない便利な機能 | kurokobo.com 公式ボットサービス利用者向けの情報 公式ボットサービスの利用者向けの情報です。なお、公式というのは AutoMuteUs の公式という意味であって、Among Us の公式ではありません。\nボットが .au コマンドで操作できない 2022 年 3 月 31 日に、公式ボットのバージョンが 7.0.0 に上がった影響で、全面的に スラッシュコマンドでの操作に置き換わり ました。これに伴い、従来の .au や @AutoMuteUs での操作はできなくなっています。\nスラッシュコマンドについては、次のエントリで紹介しています。\nAutoMuteUs 7.0 リリース： スラッシュコマンド対応などいろいろ | kurokobo.com 使えるコマンドは 公式のヘルプ で詳しく一覧されています。\n契約中のプランを確認したい 公式ボットを利用しているサーバでは、現在利用しているプランを /premium info（.au premium）コマンドで確認できます。\nブロンズ、シルバー、ゴールドプランの場合\nプラン名と残り日数が表示されます 失効している場合は失効後の経過日数が表示されます フリープランの場合\nプレミアムプランの紹介が表示されます セルフホストの場合\nセルフホストである旨が表示されます プレミアムプランを契約したのにプレミアムにならない プレミアムプランを契約したあと、/premium info（.au premium）などで状況を確認しても反映されていない場合、専用の問い合わせフォームから依頼が必要です。依頼後、中の方々が手動で対応するので、実際に反映されるまでは少し時間がかかります。\nAutoMuteUs Premium フォームを開いて、“I have purchased AutoMuteUs Premium and have NOT recieved my benefits.” から手続きします。途中、支払い時のメールに含まれる トランザクション ID と、Discord の サーバ ID が必要です。\nフォームから送信できない場合や、送信後しばらく経っても状況が変わらない場合は、AutoMuteUs のサポート Discord の #official-bot-support などで質問してください。\nゴールドプランでサーバを追加したい ゴールドプランでは、プレミアム機能を二つのサーバで有効化できますが、これにも専用の問い合わせフォームから依頼が必要です。依頼後、中の方々が手動で対応するので、実際に反映されるまでは少し時間がかかります。\nAutoMuteUs Premium フォームを開いて、“I would like to add another server with my Gold Subscription.” から手続きします。途中、支払い時のメールに含まれる トランザクション ID と、Discord の サーバ ID（現在のサーバと追加するサーバの 2 つ） が必要です。\nフォームから送信できない場合や、送信後しばらく経っても状況が変わらない場合は、AutoMuteUs のサポート Discord の #official-bot-support などで質問してください。\nプレミアムプランを別のサーバに移動させたい あるサーバで契約したプレミアムプランを別のサーバに引っ越しするにも、専用の問い合わせフォームから依頼が必要です。依頼後、中の方々が手動で対応するので、実際に反映されるまでは少し時間がかかります。\nAutoMuteUs Premium フォームを開いて、“I want to transfer my premium” から手続きします。途中、支払い時のメールに含まれる トランザクション ID と、Discord の サーバ ID（現在のサーバと移動先のサーバの 2 つ） が必要です。\nフォームから送信できない場合や、送信後しばらく経っても状況が変わらない場合は、AutoMuteUs のサポート Discord の #official-bot-support などで質問してください。\nプレミアムプランを解約したい PayPal アカウントの有無によって方法が若干異なります。\nPayPal アカウントを使って契約した場合 PayPal の定期決済・購読のキャンセル の手順に従います。AutoMuteUs 側では手続きは不要です。\n定期支払・購読の解除方法(個人)｜サポート-PayPal(ペイパル) PayPal アカウントを作らずに契約した場合 以下の専用の問い合わせフォームから依頼が必要です。依頼後、中の方々が手動で対応するので、実際に解約されるまでは少し時間がかかります。\nAutoMuteUs Premium フォームを開いて、“I would like to cancel my AutoMuteUs Premium subscription.” から “I bought premium with PayPal guest account.” を選んで手続きします。途中、支払い時に受け取ったメールに含まれる プロファイル ID と、Discord の ユーザ ID が必要です。\nフォームから送信できない場合や、送信後しばらく経っても支払いが続いている場合は、AutoMuteUs のサポート Discord の #official-bot-support などで質問してください。\n課金単位が知りたい 課金はユーザ単位ではなく サーバ単位 で、契約するとプレミアム機能も サーバ単位 で有効化されます。つまり、サーバのうち誰かひとりが契約すれば、そのサーバの全員がプレミアム機能を利用できます。\nセルフホスト利用者向けの情報 セルフホスト利用者向けの情報です。\nバージョンごとの操作方法が知りたい 公式ボットはスラッシュコマンド化されましたが、セルフホストでは、使うバージョンを変えれば任意の操作方法に変更できます。\nバージョン 操作方法 ～ 6.15.2 .au のみ 6.15.3 ～ 6.16.10 .au またはボットへのメンション 7.0.0 ～ スラッシュコマンドのみ 推奨のタグの組み合わせが知りたい 基本は最新が推奨ですが、古いバージョンを利用したい場合は、以下の組み合わせがおすすめです。\n使いたいバージョン AUTOMUTEUS_TAG GALACTUS_TAG 8.x 8.0.1 以降の最新 4.0.0 以降の最新 7.x 7.0.0 以降の最新 3.0.0 以降の最新 6.x 6.16.10 2.4.1 最新バージョンは GitHub のリポジトリ（AutoMuteUs、Galactus）で確認できます。\ndocker-compose.yml や sample.env がダウンロードできない、見つからない セルフホスト向けと開発者向けのリソースを管理するリポジトリは、2022 年 2 月に automuteus/deploy に変更されました。Discord でのボットアカウント作成手順などもこちらに移動しています。\nメジャバージョンごとに若干中身が異なるため、特にあえて 6.x を利用したい場合は注意が必要です。\n8.x 用 docker-compose.yml（ダウンロード用直リンク） sample.env（ダウンロード用直リンク） ボットアカウントの登録手順とパーミッションの説明（英語） 7.x 用 docker-compose.yml（ダウンロード用直リンク） sample.env（ダウンロード用直リンク） ボットアカウントの登録手順とパーミッションの説明（英語） 6.x 用 docker-compose.yml（ダウンロード用直リンク） sample.env（ダウンロード用直リンク） ボットアカウントの登録手順とパーミッションの説明（英語） セルフホストのボットをバージョンアップしたい 同一メジャバージョン内でのバージョンアップ 6.x 内や 7.x 内、8.x 内など、同一メジャバージョン内でバージョンアップしたい場合は、.env ファイルの AUTOMUTEUS_TAG（Galactus をバージョンアップしたい場合は GALACTUS_TAG）の書き換えと再起動で対応できます。戦績や設定などのデータは維持できます。以下、ざっくり手順です。\nはじめにボットを停止させます。\ndocker-compose down この後、.env ファイルの AUTOMUTEUS_TAG を目的のバージョンに書き換えます。\nまた、6.x 内のバージョンアップ で、6.16.1 以降にバージョンアップしたい場合のみ、docker-compose.yml の 7 行目を次のように修正します（逆に 6.16.1 以降からそれ未満に戻す場合は denverquane/amongusdiscord に戻します）。\nservices: automuteus: # Either: # - Use a prebuilt image image: automuteus/automuteus:${AUTOMUTEUS_TAG:?err} # - Build image from local source あとは元通り起動するだけです。\ndocker-compose up -d 起動したボットのバージョンは、/info（.au info）で確認できます。\n6.x から 7.x へのバージョンアップ 7.x では docker-compose.yml と .env に大きく修正が入っています。新しいパラメータも追加されており、少々複雑です。詳細は スラッシュコマンドについての紹介エントリ で触れているので、そちらを参照してください。\n7.x から 8.x へのバージョンアップ 8.x で内部の動作仕様に変更が入ったため、それに合わせて docker-compose.yml と .env も修正されています。\n通常の利用シーンで気にすべきパラメータの追加はありませんが、7.x のファイルを流用したい場合は、docker-compose.yml で環境変数 WORKER_BOT_TOKENS を galactus 下から automuteus 下に移動させる必要があります。よくわからない場合は、最新のファイル群をダウンロードして置き換えるとよいでしょう。\nAUTOMUTEUS_TAG に 6.16.1 以降を指定したら起動しない 6.16.1 から、レジストリとイメージ名が denverquane/amongusdiscord から automuteus/automuteus に変更されました。\nこれは、docker-compose.yml の 7 行目を次のように修正すると対応できます。\nservices: automuteus: # Either: # - Use a prebuilt image image: automuteus/automuteus:${AUTOMUTEUS_TAG:?err} # - Build image from local source なお、公式の docker-compose.yml（6.x 用、7.x 用、8.x 用）では修正済みなので、再度ダウンロードして置き換えてもよいでしょう。\nボットに必要なパーミッションが知りたい 最新の公式ドキュメントで、メインのボットに必要なパーミッションと、追加のワーカボットやキャプチャボットに必要なパーミッションが紹介されています。\ndeploy/BOT_README.md at main · automuteus/deploy 以前の公式のドキュメントでは Administrator 権限の付与が案内されていたため、各所の紹介もそのようになっている例が多くみられますが、これはトークンの流出や悪用に対して非常に脆弱な設定で、推奨されません。\n公式ボットとセルフホストボットを併用したい セルフホストのボットのバージョンに応じて、使い分けが可能です。\nセルフホストのボットがスラッシュコマンドに対応している場合（7.x 以降） スラッシュコマンドの実行時に対象のボットを選択して操作する セルフホストのボットがスラッシュコマンドに対応していない場合（6.x） 6.15.3 以降であれば、メンションで操作する 6.15.2 以前であれば、.au settings commandPrefix \u0026lt;任意のプレフィックス\u0026gt; でプレフィックスを変更して操作する 新色の絵文字が表示されない セルフホストの場合は、2021 年 6 月に追加された新色に対応するためには、Discord のサーバに 絵文字の空きスロットが追加で 12 必要 です。新色に対応したバージョンのボットは、ボットの起動時に不足している絵文字を自動でサーバに追加する動きをしますが、この時に空きスロットが足りなければ新色は使えないままです。\nサーバを Nitro でブーストしていない場合、絵文字のスロットは 50 が上限で、旧色と合わせるとAutoMuteUs だけで合計で 36（旧 2.4.3 系では 37）のスロットが必要なため、この上限にぶつかる例が多いようです。空きスロットはサーバの設定から確認できます。\n/new（.au new）して新色の絵文字が表示されない場合は、\nボットが絵文字を追加する権限を持っていることを確認する サーバの設定画面で足りていない絵文字（あるべき絵文字は GitHub を参照）を確認する 不要な絵文字を削除して、足りていない絵文字の数だけの空きスロットを確保する ボットを停止して起動しなおす とよさそうです。\nまた、単一のボットを複数のサーバで共用している場合 は、\n全サーバで全絵文字が追加できるだけのスロット を確保したうえで起動する または後述する 絵文字のスロットを節約したい の手順で絵文字を単一のサーバに寄せる のどちらかが必要です（技術的な詳細は次項を参照）。\nなお、絵文字の追加や削除を短時間に多数実行すると、Discord 側の処理制限に引っかかって、それ以上の絵文字の操作が一時的に拒否されることがあります。ボットの起動後、スロットに空きがあるにもかかわらず絵文字が一部しか追加されない場合は、そのまま（ボットを起動させたまま）典型的には 30 分程度放置 するか、または時間をおいてやり直すと正常に処理されます。\n技術的には、絵文字は必ずしもボットから追加させなくてもよく、リポジトリ を ZIP でダウンロードして中の assets/emojis の PNG ファイル群を手動で追加しても正常に機能します。ボットは絵文字の過不足の判定を絵文字の 名前 で行うため、名前さえ合っていれば任意の絵文字に置き換えも可能です。\n絵文字の不足は、Nitro でないアカウントであれば、次の文字列を任意のテキストチャットで投稿するとよくわかります。絵文字にならずに文字のままになったものが不足分です。\n:aubanana::aublack::aublue::aubrown::aucoral::aucyan::augray::augreen::aulime::aumaroon::auorange::aupink::aupurple::aured::aurose::autan::auwhite::auyellow: :aubananadead::aublackdead::aubluedead::aubrowndead::aucoraldead::aucyandead::augraydead::augreendead::aulimedead::aumaroondead::auorangedead::aupinkdead::aupurpledead::aureddead::aurosedead::autandead::auwhitedead::auyellowdead: 絵文字のスロットを節約したい EMOJI_GUILD_ID の話です。\n前述の通り、ボットは起動時に不足している絵文字を追加する動きをします。このとき、詳しいロジックは割愛しますが、デフォルトのままでは、自身が複数のサーバに参加している場合、絵文字の不足を確認するサーバは 実質ランダム で選ばれます。\nつまり、たとえ サーバ A にすべての絵文字がすでに揃っていても、過不足の確認にサーバ B が選出されれば（そしてサーバ B に絵文字がなければ）サーバ B に絵文字の追加処理が走る 可能性があるし、いちどボットを停止して再度起動すれば今度は サーバ C に絵文字が追加される可能性 もあるわけです。最終的に、起動停止を繰り返すと、全サーバに全絵文字が追加された状態に収束 します。\nこれが、特に何も設定しない場合のデフォルトの動きです。空きスロットが潤沢であればこれでもよいのですが、各サーバで 36 のスロットを消費するのは少し勿体ない部分です。\n一方で、Discord は External Emojis と呼ばれる、あるサーバの絵文字を別のサーバで使える機能 を持っています。一般ユーザ（人間）が使うには Nitro が必要ですが、ボットアカウントからは無償 で使えるため、AutoMuteUs もこれを使えるようにデザインされています。先の例でいえば、サーバ A の絵文字だけを常に使う ように固定し、サーバ B と C には絵文字を不要にできる ということです。\nこの設定は、設定ファイル（.env や config.txt）の EMOJI_GUILD_ID に、先の例でいう サーバ A に相当するサーバの ID を指定すると機能します。\nなお、8.3.3 以降では修正されています が、それより前のバージョンを利用している場合、EMOJI_GUILD_ID が指定されていて、かつその指定したサーバに絵文字が 不足している ときに、ボットの起動時に ほかのサーバに絵文字が追加される 動きをする 問題 がありました。\nこのため、8.3.2 以前を使っている場合は、EMOJI_GUILD_ID は目的のサーバに絵文字が全部そろっている状態を作ってから設定するほうが無難です。具体的には以下がおすすめです。\n絵文字をボット経由で追加したい場合 ボットを停止する EMOJI_GUILD_ID に指定するサーバ 以外 のすべてのサーバから、ボットをキックする ボットを起動し、不足している絵文字をすべて追加させる ボットを停止する 元通りボットを全サーバに参加させる EMOJI_GUILD_ID の設定を設定ファイルに追加して、ボットを起動する 絵文字を手で追加したい場合 EMOJI_GUILD_ID に指定するサーバに、足りていない絵文字をすべて追加する（画像は リポジトリ の assets/emojis の PNG ファイル） ボットを停止する EMOJI_GUILD_ID の設定を設定ファイルに追加して、ボットを起動する 新しい絵文字に入れ替えたい Docker 版の 6.15.2（後述の WIndows 版では 2.4.3-15.1）から、絵文字が刷新されました。\n変更前がコレで、\n変更後がこれです。\nが、単に利用するコンテナイメージ（Docker 版）や EXE ファイル（Windows 版）を差し替えるだけでは、自動では絵文字は更新されません。これは、ボット起動時のサーバ上の絵文字の有無の判定が、絵文字の 名前 を基に行われるためで、すなわち、これまでの古い絵文字がサーバに残っている限り、それが継続して使われます。\nボットを単一のサーバでのみ利用している場合は、絵文字は次の手順で新しくできます。\nサーバの Server Settings の Emoji から絵文字をいちどすべて削除する ボットを再起動する 一連の操作中、Discord の絵文字の操作回数制限に引っかかって、高い確率で一時的に絵文字の削除や追加ができない状態になりますが、しばらく（典型的には 30 分以上）時間をあければ再度操作できるようになります。\nボットを複数のサーバで共用している場合も基本的な考え方は同じですが、諸々考慮事項があり、環境によっては上記手順では意図しない状態になる可能性があります。できるだけ前述の 新色の絵文字が表示されない や 絵文字のスロットを節約したい も併せて参照し、仕様を把握したうえで状態の遷移に留意しつつ作業するとよいでしょう。\n例えば、EMOJI_GUILD_ID を設定済みの環境であれば、絵文字はボットには追加させず、上記 1 と 2 の間で手動で追加（リポジトリ を ZIP でダウンロードして中の assets/emojis の PNG ファイル群を絵文字としてアップロード）してしまうのが個人的にはおすすめです。\n旧 Windows 版（2.4.3）で 15 人ロビーや新色を使いたい Docker を使わずにセルフホストできる手段として、これまでは各所で旧バージョンである 2.4.3 が紹介されていました。しかしながら、今後 2.4.3 が公式に更新されて新色に対応する予定はない ため、次のいずれかの対応が必要です。\nセルフホストをやめて、公式サービスを利用 する Windows 版ではなく、公式の Docker 版を利用 したセルフホストに変更する 有志が作成した非公式版 を利用する 第三案である 有志が作成した非公式版 は、現状では以下が選択肢です。\n公式の旧 2.4.3 に新色対応を追加した非公式 Windows 版（lewymd/automuteus） automuteus_windows.exe をダウンロードして、従来の同名のファイルを置き換えるだけで利用可能 公式の Docker 版をリビルドして Windows で利用できるようにした AutoMuteUs Portable 設定で AUTOMUTEUS_TAG に 6.15.1 以降を指定すれば新色に対応 利用できる機能は Docker 版と同等で、プレミアム機能も利用可 .env を修正しても設定が反映されない .env ファイルを修正したあとは、AutoMuteUs のコンテナを再作成する必要があります。再作成するには、次のコマンドを実行します。\ndocker-compose down docker-compose up -d なお、コンテナを 再起動 するコマンドとして docker-compose restart がありますが、設定の変更を反映させる目的では（ファイルの再読み込みが行われないため）効果がありません。明示的に down と up を行う必要がある点に注意が必要です。\n新しいプレイヤーの統計が保存されない セルフホストのボットを 7.1.2 以前から 7.2.0 以降にアップグレードした場合、アップグレード 後 に初めてそのボットを使い始めた新しいプレイヤの統計が正常に保存されないバグがありました。\nこの問題は、7.2.7 で修正されています。\n全部初期化してやり直したい 内部で利用しているデータベース（PostgreSQL、Redis）のデータが永続ボリューム上にあるため、AutoMuteUs の初期化にはこのボリュームを含めたコンテナの削除が必要です。\nDocker Compose の通常の停止コマンド docker-compose down では永続ボリュームは削除されませんが、これに --volumes（または -v）を追加すると、永続ボリュームごと削除できます。\ndocker-compose down --volumes いろいろいじくってよくわからなくなったときや、統計も設定も含め何もかも消して初期化したいときなどには便利です。\nデータをバックアップしたい・リストアしたい・移行したい 次の四つのバックアップとリストアを考えれば要件は満足できます。\n.env .docker-compose.yml（カスタマイズしている場合） PostgreSQL のダンプ Redis のダンプ 具体的なバックアップとリストアの手順は、ぼくのリポジトリでガイドを載せています。\nBackup and Restore 環境間の移行などでも応用可能です。\n負荷を監視したい AutoMuteUs は組み込みで Prometheus のコレクタ（！）を持っているので、それと Galactus のエンドポイントが返す JSON などを使うと、こういうモニタリング画面が比較的簡単に作れます。\n実装サンプルはぼくのリポジトリでガイドしています。\nAutoMuteUs with Grafana Dashboard using Prometheus Raspberry Pi で動かしたい Docker 版のうち、以下は ARM 版のイメージが提供されています。\n6.x のうち、6.15.3 以前 7.x 以降のすべて 6.x のうち 6.15.4 以降は、ARM 版のイメージが提供されていないので、必要に応じて自分でビルドする必要があります。\nまた、32 ビットの Raspberry Pi OS（旧 Raspbian）で動かそうとすると、PostgreSQL と Redis がうまく動かないことがあります。Alpine 由来のトラブルで、一部のパッケージ更新で対応できます。\nこちらも詳しくはリポジトリでガイドしています。\nAutoMuteUs on Raspberry Pi 正規の証明書で HTTPS 化したい 以前のエントリで書いています。\nAmong Us の便利ボット AutoMuteUs の通信をリバースプロキシ（Nginx/Caddy）と Let’s Encrypt で暗号化する | kurokobo.com GitHub から資材をダウンロードしたい セルフホスト向けの手順で、GitHub から Docker Compose 用のファイルをダウンロードするために GitHub のアカウント作成や SSH の鍵登録を紹介している例があるようですが、過剰に難しくなってしまいもったいないので、補足です。\ngit コマンドを使わずに対応する リポジトリのページ の から Download ZIP で ZIP ファイルをダウンロードして、解凍すると取り出せます または、docker-compose.ymlと sample.env を直接保存するのも手です git コマンドを使って対応する git コマンドが使えるようにインストールして、以下のコマンドを実行するだけで大丈夫です git clone https://github.com/automuteus/deploy.git GitHub のアカウントや SSH の鍵登録は不要です AutoMuteUs 関連おすすめエントリ AutoMuteUs 7.3 の新機能： データベース内のデータのダウンロード AutoMuteUs 7.0 のリリース： スラッシュコマンド対応などいろいろ AutoMuteUs のよくある質問と回答とかいろいろ Among Us 用ボット AutoMuteUs のあまり知られていない便利な機能 ","date":"2021-07-05T17:32:03Z","image":"/archives/3863/img/image-246.jpg","permalink":"/archives/3863/","title":"AutoMuteUs のよくある質問と回答とかいろいろ"},{"content":"概要 あるゲームのアップデートの配信をいちはやく検知したい要件が発生したので、そういうツールを作りました。\nGame Update Notifier 現時点で、Steam と Epic Games と Microsoft Store に対応しています。事前に指定したゲームのアップデート有無を一定の間隔で監視し、アップデートがあった場合に Discord でメンションを飛ばして通知してくれます。\nこのツールは、詳細なパッケージやバージョンの生の情報を取得して監視 するため、既存のツールと違って、リリースノートの更新などを伴わない小さなアップデートでも検知できる のが大きな特徴です。\nサポートしているプラットフォーム 監視できるプラットフォームと通知先は次の通りです。\n監視対象 Steam Microsoft Store Epic Games（要アカウント、保有しているゲームのみ） 通知先 Discord（ユーザ宛、ロール宛のメンション） Steam と Microsoft Store は、アカウントがなくても（自身の所有有無に関わらず）監視できます。Epic Games はアカウントが必須で、監視対象にできるのは自身が保有しているゲームのみです。\n使い方 詳細は GitHub 上の README に書いたのでそちらを参照してもらえればよいですが、簡単に紹介します。\n環境要件とツールの準備 現状、Docker（または Podman）と Docker Compose を前提にしています。実装は Python です。\nGame Update Notifier の準備 (1) Game Update Notifier のリポジトリをクローンするか、リポジトリ内の docker-compose.yml と sample.env をダウンロードして、sample.env を .env としてコピー（またはリネーム）します。\nDiscord の準備 通知を投稿したいチャネルの Webhook URL を取得します。\nIntro to Webhooks – Discord 併せて、通知にメンションを含めたい場合は、対象ユーザの ID や 対象ロールの ID を取得します。\nWhere can I find my User/Server/Message ID? – Discord メンションは必須ではないのでなくてもよく、また、それぞれ複数指定も可能です。ロールの ID は、上記リンク先に従って Developer Mode を有効化したあと、ロールの一覧画面で右クリックして Copy Id を選択すれば取得できます。\n監視したいゲームの ID の準備 監視したいゲームの ID を、監視したいプラットフォームごとに取得します。プラットフォームごとに手順が異なります。\nSteam のゲームの ID Steam のストアページ で、目的のゲームを開きます。URL に含まれる数字が ID です。例えば、Outer Wilds の URL は https://store.steampowered.com/app/753640/Outer_Wilds/ で、ID は 753640 です。\nなお、バンドル版の場合は、バンドルパッケージの ID ではなく、バンドルされている単体版の ID を取得します。例えば、Among Us Starter Pack の ID (16867) ではなく、単体版の Among Us の ID (945360) を取得します。\nID が用意できたら、本ツールに含まれるヘルパスクリプトを使って、ゲームごとのリリースブランチ名を確認します。-i で調べた ID を与えます。\n$ docker-compose run --rm notifier helper/app_finder.py -p steam -i 753640 KEY App Id Name Branch Updated Time -------------- -------- ----------- -------- -------------- 753640:public 753640 Outer Wilds public 1595281461 753640:neowise 753640 Outer Wilds neowise 1603749868 753640:staging 753640 Outer Wilds staging 1594088316 通常は public ですが、ベータ版を監視したい場合などはそれっぽいヤツを選びます（ブランチ名はゲームによって異なるので、一概にコレとは言いにくいようです）。\n監視したいブランチの KEY 列の値を控えておきます。この値は後で利用します。\nEpic Games のゲームの ID Epic Games の監視には、自分のアカウントでログインする必要があります。まずはヘルパスクリプトを使って、認証します。\n$ docker-compose run --rm notifier helper/epicgames_auth.py [cli] INFO: Testing existing login data if present... Please login via the epic web login! If web page did not open automatically, please manually open the following URL: https://www.epicgames.com/id/login?redirectUrl=https://www.epicgames.com/id/api/redirect Please enter the \u0026#34;sid\u0026#34; value from the JSON response: 14b8c***********************8fb5 [cli] INFO: Successfully logged in as \u0026#34;\u0026lt;Your Account\u0026gt;\u0026#34; 実行後、出力される URL にアクセスして自分のアカウントで認証し、表示された sid をツールの入力欄に戻します。\nログインが成功したら、所有しているゲームの一覧を取得します。\n$ docker-compose run --rm notifier helper/app_finder.py -p epicgames [Core] INFO: Trying to re-use existing login session... KEY App Id Name Build Version -------------------------------- -------------------------------- --------------------------- ------------------ 963137e4c29d4c79a81323b8fab03a40 963137e4c29d4c79a81323b8fab03a40 Among Us 2021.5.25.2 bcbc03d8812a44c18f41cf7d5f849265 bcbc03d8812a44c18f41cf7d5f849265 Cities: Skylines 1.13.3-f9 Kinglet Kinglet Sid Meier\u0026#39;s Civilization VI 1.0.12.564030h_rtm 監視したいゲームの KEY 列の値を控えておきます。この値は後で利用します。\nMicrosoft Store のゲームの ID Microsoft Store のストアページ で、目的のゲームを開きます。URL に含まれるランダム文字列が ID です。例えば、Minecraft for Windows 10 の URL は https://www.microsoft.com/en-us/p/minecraft-for-windows-10/9nblggh2jhxj で、ID は nblggh2jhxj です。\nなお、バンドル版の場合は、バンドルパッケージの ID ではなく、バンドルされている単体版の ID を取得します。例えば、Minecraft for Windows 10 Starter Collection の ID (9n4km90ctzt6) ではなく、単体版の Minecraft for Windows 10 の ID (9nblggh2jhxj) を取得します。\nID が用意できたら、本ツールに含まれるヘルパスクリプトを使って、ゲームごとのプラットフォーム名を確認します。-i で調べた ID を与えます。\n$ docker-compose run --rm notifier helper/app_finder.py -p msstore -m JP -i 9nblggh2jhxj KEY App Id Market Name Platform Package Name ------------------------------ ------------ -------- ------------------------ ----------------- ------------------------------------------------------- 9nblggh2jhxj:Windows.Xbox 9nblggh2jhxj JP Minecraft for Windows 10 Windows.Xbox Microsoft.MinecraftUWP_1.16.22101.70_x86__8wekyb3d8bbwe 9nblggh2jhxj:Windows.Universal 9nblggh2jhxj JP Minecraft for Windows 10 Windows.Universal Microsoft.MinecraftUWP_1.16.22101.0_x86__8wekyb3d8bbwe 9nblggh2jhxj:Windows.Universal 9nblggh2jhxj JP Minecraft for Windows 10 Windows.Universal Microsoft.MinecraftUWP_1.16.22101.0_arm__8wekyb3d8bbwe 9nblggh2jhxj:Windows.Xbox 9nblggh2jhxj JP Minecraft for Windows 10 Windows.Xbox Microsoft.MinecraftUWP_1.16.22101.70_arm__8wekyb3d8bbwe 9nblggh2jhxj:Windows.Universal 9nblggh2jhxj JP Minecraft for Windows 10 Windows.Universal Microsoft.MinecraftUWP_1.16.22101.0_x64__8wekyb3d8bbwe 9nblggh2jhxj:Windows.Xbox 9nblggh2jhxj JP Minecraft for Windows 10 Windows.Xbox Microsoft.MinecraftUWP_1.16.22101.70_x64__8wekyb3d8bbwe 通常は Windows.Desktop ですが、上記のように Windows.Universal の場合もあるようです。\n監視したいプラットフォームの KEY 列の値を控えておきます。この値は後で利用します。\nGame Update Notifier の準備 (2) 前述の準備 (1) で用意した .env に、値を追記します。代表的なモノだけ抜粋します。\n項目 説明 GAME_UPDATE_NOTIFIER_TAG リリースページ の最新のタグ（現時点だと 0.0.3）を指定します IGNORE_FIRST_NOTIFICATION 仕様上、初回起動時は必ず変更として扱われるため、この時の通知を無効化する場合は true、無効化しない場合は false を指定します。初回起動時に通知のテストを兼ねたい場合は false がおすすめです。 CHECK_INTERVAL_SEC 監視間隔を指定します。デフォルトの 300 秒（5 分）くらいがおすすめです。 DISCORD_WEBHOOK_URL 通知を送る Discord の Webhook の URL を指定します。 DISCORD_MENTION_ROLE_IDS Discord での通知時にメンションするロールの ID を指定します。カンマ区切りで複数指定できます。 DISCORD_MENTION_USER_IDS Discord での通知時にメンションするユーザの ID を指定します。カンマ区切りで複数指定できます。 WATCH_STEAM Steam のゲームを監視する場合は true、監視しない場合は false を指定します。 STEAM_APP_IDS 前述の手順で調べた Steam のゲームの ID（KEY 列の値）を指定します。カンマ区切りで複数指定できます。 WATCH_MSSTORE Microsoft Store のゲームを監視する場合は true、監視しない場合は false を指定します。 MSSTORE_APP_IDS 前述の手順で調べた Microsoft Store のゲームの ID（KEY 列の値）を指定します。カンマ区切りで複数指定できます。 MSSTORE_MARKET 情報を取得する Microsoft Store のマーケットを指定します。日本の場合は JP です。 WATCH_EPICGAMES Epic Games のゲームを監視する場合は true、監視しない場合は false を指定します。 EPICGAMES_APP_IDS 前述の手順で調べた Epic Games のゲームの ID（KEY 列の値）を指定します。カンマ区切りで複数指定できます。 Game Update Notifier の起動 Epic Games のゲームを監視する場合は、前述のゲームの ID の準備の手順に従って認証をします（Epic Games のげ監視をしない場合は不要です）。\nあとは docker-compose up -d するだけです。\ndocker-compose up -d 動き アップデートが検知されると、Discord に通知が投稿されます。\nAmongUsCapture のオフセット更新のため、Among Us のアップデートの監視に使っていますが、便利です。\n細かい動作仕様などは GitHub を覗いてください。バグがあれば Issue 起票なども歓迎です。\n","date":"2021-06-19T14:31:02Z","image":"/archives/3850/img/gun.png","permalink":"/archives/3850/","title":"Game Update Notifier： ゲームのアップデートを監視して Discord で通知する（Steam / Epic Games / Microsoft Store 対応）"},{"content":"はじめに 前回のエントリ では、AWX をほどほどの使用感でシングルノード K3s 上にホストする方法を紹介しました。\nこの中では、バックアップとリストアのごく簡易的な例として、PV の実体をフルバックアップする考え方や pg_dump での運用を紹介しましたが、エントリ中でも記述していた通り、AWX の 0.10.0 からは、組み込みで バックアップ と リストア の機能が追加されています。\n本エントリでは、この AWX Operator によるバックアップとリストアを実践します。\n仕組み AWX の 0.10.0 から、カスタムリソース AWXBackup と AWXRestore が追加 されました。バックアップとリストアは、これら AWXBackup リソースと AWXRestore リソースの作成によって実行できます。\nバックアップを取得するには、AWXBackup リソースを作成します。ひとつの AWXBackup リソースがひとつのバックアップに相当し、実際には指定した PV 内に次のファイル群を含んだディレクトリが作成されます。\nAWX インスタンスの構成情報 構成に必要なシークレット情報 PostgreSQL のダンプ リストアを実行するには、リストアしたいバックアップに対応する AWXBackup リソースを指定して AWXRestore リソースを作成します。新規環境へのリストア時など AWXBackup リソースが存在していない場合でも、前述のファイル群があれば、PV 名やディレクトリ名を直接指定してリストアが行えます。\n具体的な利用方法や設定は、それぞれ対応するロールの README.md で確認できます。\nbackup ロールの README.md restore ロールの README.md バックアップの取得 今回は、前回のエントリ の構成を前提にします。必要なファイルは前回同様 GitHub 上のリポジトリ にも配置しています。\nバックアップの準備 バックアップの保存先となる PV を準備します。PV の実体のディレクトリをローカルで作成し、これに対応する PV と PVC を事前に作成しておきます。\nsudo mkdir -p /data/backup --- apiVersion: v1 kind: PersistentVolume metadata: name: awx-backup-volume spec: accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain capacity: storage: 2Gi storageClassName: awx-backup-volume hostPath: path: /data/backup --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: awx-backup-claim spec: accessModes: - ReadWriteOnce volumeMode: Filesystem resources: requests: storage: 2Gi storageClassName: awx-backup-volume 今回は外部ストレージを利用しないシングルノード構成なので、PV は hostPath で構成しています。\nバックアップの取得 AWXBackup リソースのマニフェストを作成して kubectl apply -f すると、バックアップが実行できます。ここでは次のようなマニフェストを作成します。細かいパラメータは ドキュメント に記載があります。\n--- apiVersion: awx.ansible.com/v1beta1 kind: AWXBackup metadata: name: awxbackup-2021-06-06 namespace: awx spec: deployment_name: awx backup_pvc: awx-backup-claim postgres_label_selector: app.kubernetes.io/instance=postgres-awx ひとつの AWXBackup リソースがひとつのバックアップに相当するため、metadata.name はそのバックアップを特定できるユニークな名称にします。ここではハードコードしていますが、実際には実行日時などを外部の仕組みで埋め込んで実行するとよさそうです。また、spec.backup_pvc で、事前に作成した PVC を指定しています。\nなお、spec.postgres_label_selector は、0.10.0 の段階ではデフォルトの値に誤りがあるため、postgres-\u0026lt;AWX 名\u0026gt; の文字列での明示が必須のようでした。アップストリームでは修正済みなので、次期リリースではデフォルトでよければ不要になるでしょう（追記： 0.11.0 で修正されました）。\nマニフェストができたら、apply します。\nkubectl apply -f awxbackup.yaml 進捗は AWX Operator のログで確認できます。\n$ kubectl logs -f deployment/awx-operator ... --------------------------- Ansible Task Status Event StdOut ----------------- PLAY RECAP ********************************************************************* localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=7 rescued=0 ignored=0 ------------------------------------------------------------------------------- これで、AWXBackup リソースができあがります。実際の PV の中身を探索すると、実行日時を含むディレクトリにバックアップファイルが保存されていることもわかります。なお、AWXBackup リソースと実際の PV 内のディレクトリの対応付けは、describe などで確認できます。\n$ kubectl -n awx get awxbackup NAME AGE awxbackup-2021-06-06 6m47s $ ls -l /data/backup/tower-openshift-backup-2021-06-06-10\\:51\\:49/ total 736 -rw-r--r--. 1 root root 749 Jun 6 06:51 awx_object -rw-r--r--. 1 root root 482 Jun 6 06:51 secrets.yml -rw-------. 1 systemd-coredump root 745302 Jun 6 06:51 tower.db これでバックアップは取得できた…… のですが、構築時に事前に手動で作成したシークレット の中身は、AWX Operator でのバックアップには 0.10.0 の段階では 含まれない ようです（Issue は上がっているので、将来的には是正されそうではあります）。このため、今回の構成 では、Ingress で利用する TLS のシークレットは、別途取得が必要です。YAML ファイルでエクスポートしてもよいですし、もしくは構築時に利用したもともとのファイルを取っておくのもよいでしょう（追記： 0.13.0 以降では、この問題は修正されています）。\nkubectl -n awx get secret awx-secret-tls -o yaml \u0026gt; awx-secret-tls.yaml リストアの実行 リストアも、ここでは 前回のエントリ の構成を前提にします。必要なファイルはバックアップ同様 GitHub 上のリポジトリ にも配置しています。\nリストアの準備 既存の環境に対して上書きリストアをするのであれば、特別な事前準備は不要です。\n新規環境に対してゼロからリストアをするのであれば、少なくともリストア先の PV と PVC は 前回のエントリ などを参照して事前に作成しておきます。面倒であれば、初期構築時のファイル一式をそのまま使って、新しいインスタンスをいちどデプロイしてしまうのも手です。\nまた、バックアップデータが保存されている PV も存在しない場合は、前述のバックアップの準備と同様の手法で用意し、必要なファイルを配置しておきます。\nリストアの実行 リストアの実行には、バックアップのときと同様に、AWXRestore リソースのマニフェストを作成して apply するだけですが、AWXBackup リソースの残存有無でマニフェストに含むべき内容が少し変わります。\nAWXBackup リソースが残存している状態でのリストア マニフェスト中で、リストアする AWXBackup リソースを指定します。\n--- apiVersion: awx.ansible.com/v1beta1 kind: AWXRestore metadata: name: awxrestore-2021-06-06 namespace: awx spec: deployment_name: awx backup_pvc_namespace: awx backup_name: awxbackup-2021-06-06 AWXBackup リソースが残存していない状態でのリストア マニフェスト中で、バックアップデータを含む PV に対応する PVC と、その中のパスを指定します。\n--- apiVersion: awx.ansible.com/v1beta1 kind: AWXRestore metadata: name: awxrestore-2021-06-06 namespace: awx spec: deployment_name: awx backup_pvc_namespace: awx backup_pvc: awx-backup-claim backup_dir: /backups/tower-openshift-backup-2021-06-06-10:51:49 なお、指定した PVC は Pod の /backup にマウントされるため、パスはその前提で記載が必要です。\n実行 apply します。\nkubectl apply -f awxrestore.yaml 結果はバックアップ時と同様にログで確認できます。インスタンスがまるごと再作成されるようで、初期構築と同等かそれ以上の時間がかかります。\n$ kubectl logs -f deployment/awx-operator --------------------------- Ansible Task Status Event StdOut ----------------- PLAY RECAP ********************************************************************* localhost : ok=53 changed=2 unreachable=0 failed=0 skipped=30 rescued=0 ignored=0 ------------------------------------------------------------------------------- Ingress の TLS など、別途取得していたバックアップがあれば、併せてリストアして、完了です（追記： 0.13.0 以降ではこの手順は不要です）。\nkubectl apply -f awx-secret-tls.yaml リストアのオブジェクトは残りますが、不要なら削除できます。\n$ kubectl -n awx get awxrestore NAME AGE awxrestore-2021-06-06 137m まとめ AWX Operator を使った AWX のバックアップとリストアを紹介しました。まだ GA していないので今後実装が大きく変わる可能性は当然ありますが、今回のようにシンプルな構成であれば、充分に使っていけそうです。\n","date":"2021-06-13T13:33:18Z","image":"/archives/3830/img/image-248.jpg","permalink":"/archives/3830/","title":"AWX Operator による AWX のバックアップとリストア"},{"content":"やりたかったこと AWX の 18.0 から、インストールには AWX Operator の利用、すなわち Kubernetes や OpenShift 上へのデプロイが推奨されるようになりました。それ以前のバージョンでは Docker Compose ベースのデプロイも選択肢として用意されていましたが、現在では（開発やテスト目的を除いて）非推奨になっています。\nとはいえ、気軽に使いたいだけ、シングルノードの可用性で充分、などの軽めのユースケースに対しては、すでに Kubernetes を使っている環境でない限り、AWX のためだけに Kubernetes 環境を作る（そして運用していく）のも少々大袈裟で大変です。かといって、公式のインストール手順 で気軽に試せる手段として紹介されている minikube は、プロダクション用途が想定されていない開発や学習用のツールのため、AWX をもう一歩踏み込んで日常的に使っていきたい場合には、逆に少し心許ないところがあります。\nそんなわけで、ふたつの選択肢の間でほどよく使えるように、\nシングルノード で 外部からもアクセス できて データも永続化 できて それなりに手堅く 使っていける 感じのを、K3s で作ることにしました。\nAWX on minikube, K3s, MicroK8s, or k0s AWX を 公式のインストール手順 に従って minikube で構成した場合、実際に日常で使っていける AWX としての仕上がりを期待してしまうと、現実とは少しギャップが生じます。\nminikube はもともとローカル環境での学習や開発を目的にしたツールのため、その目的に沿った利用であれば非常に軽量かつ快適に利用でき、事実、たいへん便利です。一方で、根本的にはプロダクション用途が想定されていないこともあり、外部からのアクセスやデータの永続性に関しては機能が限定的で、学習用途からもう一歩踏み込んだ利用には少し不安感が出てきます（オプションでコントロールは可能ですが、そもそものツールの仕様上、ネットワークが少し複雑になりがちです）。\n対して K3s は、いわゆる Kubernetes の軽量版ディストリビューションのひとつで、IoT やエッジコンピューティングなどのユースケースではプロダクション用途も想定されています。元の Kubernetes と比較すると軽量化のために機能が削減されてはいますが、シングルノードでも構成でき、今回のように、\nプロダクション用途でも使える なるべく手軽（凝った使い方ができなくてもよい）に すぐ使い始められる Kubernetes 環境 としても、とてもよい選択肢です。もちろんマルチノード構成も組めるので、さらに本格的な構成や様々な使い方にも対応できます。\n類似のディストリビューションではほかにも MicroK8s、k0s などがあります。どちらもシングルノードで動作させられますが、前者はフル機能な Kubernetes になるのでちょっと過剰、後者は日が浅いのでちょっと見送り…… な感じで、今回は K3s に落ち着いています。\n目指す AWX の形 手軽とはいいつつまじめに使える状態には仕上げたいので、ほどよいバランスを目指して、今回の AWX は次のような要件で考えます。\n1 台の CentOS 8 上で動作する HTTPS で外部からアクセスできる データが永続化される ローカルディスク上（hostPath）でよい その代わり PV を消してもデータがホスト上に残ってほしい パスワード類をあらかじめ指定できる 全損しても元のデータと元のパスワードで作り直せばきれいに復元されてほしい コンポーネントのバージョンを指定できる 全損したときに任意のバージョンで復元できてほしい まとめると、今回は データやパスワードのオーナシップを人間側に寄せる ことを要件にしています。パスワード類は事前に指定しなければランダム文字列で生成されますが、データをそのままに丸ごと作り直したい場合などに多少面倒になる（古い PostgreSQL の認証情報を別のシークレットで渡す必要がある）ので、決め打ちできるようにしてしまっています。\nただし、理想を言えばこの要件はむしろ悪手 で、データにしてもパスワードにしても、障害時のリカバリを含め、本来はなるべく人間の制御下からは離す方向で考えるほうが望ましいでしょう（後述しますが AWX Operator も組み込みでバックアップとリストアの仕組みが実装されているようで、それらを併用すれば人間の管理負荷も削減できそうです）。\nホストの準備と K3s の導入 今回は CentOS 8 をホストとして利用するため、ミニマム構成でインストールして適当に用意します。ハードウェアの最小要件は Ansible Tower のドキュメント では 2 CPU / 4 GB RAM ですが、今回は大きめに 4 CPU / 8 GB RAM にしています。\nK3s のドキュメント に従って、Firewalld を無効化して停止します。\nsudo systemctl disable firewalld --now K3s のインストールスクリプトをダウンロードして実行します。kubectl 用に作成される設定ファイル（/etc/rancher/k3s/k3s.yaml）を作業用の一般ユーザからも読み取れるようにするため、ここでは --write-kubeconfig-mode 644 オプションを与えています（ドキュメントに記載 があります）。\ncurl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644 完了すると、バイナリの導入のほか種々の設定が行われ、K3s が利用できる状態になります。Systemd にもサービス K3s が登録され、自動起動が有効化されます。また、kubectl コマンドも利用できるようになります（この kubectl の実体は、/usr/local/bin/k3s へのシンボリックリンクで、既定で /etc/rancher/k3s/k3s.yaml を読むなど、いくつかアップストリームのそれとは異なる独自の仕様を持っています）。\nなお、--write-kubeconfig-mode 644 オプション付きで K3s を導入すると、ホストにログインできる誰でも管理権限で kubectl を叩けるようになります。操作ユーザを限定したいのであればこのオプションは使わず、導入後に /etc/rancher/k3s/k3s.yaml を ~/.kube/config にコピーして KUBECONFIG 環境変数で指定するなど、何らかの工夫が必要です。\nAWX Operator の導入 K3s の導入が終われば、すでにシングルノードの Kubernetes が利用できる状態になっているので、ここからは AWX の導入です。\nAWX のインストール手順 に従い、AWX Operator を構成します。ここでは執筆時点の最新の 0.10.0 を利用しています。なお、AWX Operator のバージョンは AWX のバージョンにも紐づく（異なる組み合わせでの利用はサポートしていない）ので、リストア目的で環境を再構築する場合は、この段階からバージョンを気にする必要があります。AWX Operator の 0.10.0 は、AWX の 19.2.0 に対応しているようです（installer ロールの変数ファイル から対応バージョンが読み取れます）。\nkubectl apply -f https://raw.githubusercontent.com/ansible/awx-operator/0.10.0/deploy/awx-operator.yaml これで、デフォルトのネームスペース（通常は default）に awx-operator がデプロイされます。\n$ kubectl get deployment NAME READY UP-TO-DATE AVAILABLE AGE awx-operator 1/1 1 1 30s 追記（2021/10/06） AWX Operator の 0.14.0 から、AWX Operator のデプロイに make を使うようになりました。\n# make のインストール sudo dnf install -y git make # リポジトリのクローンと特定のバージョンのチェックアウト git clone https://github.com/ansible/awx-operator.git cd awx-operator git checkout 0.14.0 # AWX Operator が動作する名前空間の指定とデプロイ export NAMESPACE=awx make deploy AWX のデプロイ AWX Operator の導入でカスタムリソース AWX も利用可能になるので、雑に言えば、あとは AWX リソースのマニフェストを書いて kubectl apply すれば完成です。ミニマムでは、マニフェストは AWX Operator のドキュメント に記載のある数行だけです。\nが、今回は前述の要件の通り少しカスタマイズを入れたいので、ドキュメント を参照しながらマニフェストに手を入れていきます。いくつかマニフェストファイルを作ってから、Kustomize でまとめて流す作戦で考えます。\n詳解しているファイル群はまとめて GitHub 上のリポジトリ でも公開しています。\nAWX リソースの準備（awx.yaml） AWX リソースのマニフェストを用意します。中で参照させているシークレットや PV、PVC については後述します。\n--- apiVersion: awx.ansible.com/v1beta1 kind: AWX metadata: name: awx spec: admin_user: admin admin_password_secret: awx-admin-password ingress_type: ingress ingress_tls_secret: awx-secret-tls hostname: awx.example.com postgres_configuration_secret: awx-postgres-configuration postgres_storage_class: awx-postgres-volume postgres_storage_requirements: requests: storage: 2Gi projects_persistence: true projects_existing_claim: awx-projects-claim なお、このパラメータ名は AWX Operator の 0.10.0 準拠ですが、以前の 0.9.0 までは、パラメータ名にすべて tower_ が接頭辞としてついていました。古いバージョンを利用する場合は置き換えが必要です。\nNamespace リソースの準備（namespace.yaml） 作ったり消したりしやすくなるので、ネームスペースを用意します。\n--- apiVersion: v1 kind: Namespace metadata: name: awx PersistentVolume リソースとディレクトリの準備（pv.yaml、pvc.yaml） 前述の awx.yaml で参照しているふたつの PV（PostgreSQL 用、プロジェクト用）と、そのうちプロジェクト用の PV のための PVC のマニフェストです。PostgreSQL 用の PVC は事前に定義できないようなので、クラス名で紐づけさせています。\n--- apiVersion: v1 kind: PersistentVolume metadata: name: awx-postgres-volume spec: accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain capacity: storage: 2Gi storageClassName: awx-postgres-volume hostPath: path: /data/postgres --- apiVersion: v1 kind: PersistentVolume metadata: name: awx-projects-volume spec: accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain capacity: storage: 2Gi storageClassName: awx-projects-volume hostPath: path: /data/projects --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: awx-projects-claim spec: accessModes: - ReadWriteOnce volumeMode: Filesystem resources: requests: storage: 2Gi storageClassName: awx-projects-volume 併せて、PV 中で hostPath で指定しているディレクトリも作成します。プロジェクトディレクトリは UID が 1000 の awx ユーザが触ることになるので、その UID で作成します。\nsudo mkdir -p /data/postgres sudo mkdir -p /data/projects sudo chown 1000:0 /data/projects なお、K3s では Local Path Provisioner がデフォルトで動作しており、PV を手で用意しなくても PVC に対して動的に PV をプロビジョニングできますが、\npersistentVolumeReclaimPolicy が Delete になる（PV を消すと実データも消える） accessModes が ReadWriteMany の PVC に対応できない PV 名が予測できない（/var/lib/rancher/k3s/storage 下の GUID ベースのディレクトリとして作成される） ので、今回は使っていません。\nサーバ証明書の準備（tls.crt、tls.key） AWX のインタフェイスを Ingress で HTTPS 化するために、サーバ証明書を作ります。ここでは自己署名証明書を作っていますが、正規のものでももちろんよいです。\nopenssl コマンドで作成します。\nAWX_HOST=\u0026#34;awx.example.com\u0026#34; openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -out tls.crt -keyout tls.key -subj \u0026#34;/CN=${AWX_HOST}/O=${AWX_HOST}\u0026#34; -addext \u0026#34;subjectAltName = DNS:${AWX_HOST}\u0026#34; Kustomize の準備（kustomization.yaml） ここまでで作った色々をまとめて放り込むために、kustomization.yaml を作成します。冒頭で namespace を指定して、そのネームスペース内に閉じ込めています。\nまた、awx.yaml 中で指定していたシークレットリソースもこの中で作成させます。PostgreSQL の認証情報と AWX の管理者の初期パスワードもここであらかじめ作成させます（ランダムでよければ当該ブロックは丸ごと削除できます）。\n--- apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization namespace: awx generatorOptions: disableNameSuffixHash: true secretGenerator: - name: awx-secret-tls type: kubernetes.io/tls files: - tls.crt - tls.key - name: awx-postgres-configuration type: Opaque literals: - host=awx-postgres - port=5432 - database=awx - username=awx - password=Ansible123! - type=managed - name: awx-admin-password type: Opaque literals: - password=Ansible123! resources: - namespace.yaml - pv.yaml - pvc.yaml - awx.yaml デプロイ Kustomize を実行します。\n$ kubectl apply -k . namespace/awx created secret/awx-admin-password created secret/awx-postgres-configuration created secret/awx-secret-tls created awx.awx.ansible.com/awx created persistentvolume/awx-postgres-volume created persistentvolume/awx-projects-volume created persistentvolumeclaim/awx-projects-claim created うまくいけば AWX Operator の Pod のログに Ansible っぽいアレが出て完了します。\n$ kubectl logs -f deployment/awx-operator ... --------------------------- Ansible Task Status Event StdOut ----------------- PLAY RECAP ********************************************************************* localhost : ok=51 changed=2 unreachable=0 failed=0 skipped=32 rescued=0 ignored=0 ------------------------------------------------------------------------------- 追記（2021/10/06） AWX Operator 0.14.0 以降では、ログは次のコマンドで確認できます。\nkubectl -n awx logs -f deployments/awx-operator-controller-manager -c manager awx ネームスペースにいろいろできあがります。\n$ kubectl get all -n awx NAME READY STATUS RESTARTS AGE pod/awx-postgres-0 1/1 Running 0 4m30s pod/awx-b47fd55cd-d8dqj 4/4 Running 0 4m22s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/awx-postgres ClusterIP None \u0026lt;none\u0026gt; 5432/TCP 4m30s service/awx-service ClusterIP 10.43.159.187 \u0026lt;none\u0026gt; 80/TCP 4m24s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/awx 1/1 1 1 4m22s NAME DESIRED CURRENT READY AGE replicaset.apps/awx-b47fd55cd 1 1 1 4m22s NAME READY AGE statefulset.apps/awx-postgres 1/1 4m30s DNS か hosts ファイルでホスト名の名前解決をできるようにして、ブラウザで HTTPS でアクセスすると、ログイン画面が表示され、ユーザ名 admin と指定したパスワードでログインできます。\n楽しいですね。\n管理用パスワードを事前に指定しなかった場合は、シークレットの値をデコードして取得できます。\n$ kubectl get secret awx-admin-password -o jsonpath=\u0026#34;{.data.password}\u0026#34; -n awx | base64 --decode Mp1UtHgMqxRnDGzcz0fy3e4RBePgL96S 簡易バックアップと簡易リストア AWX Operator の 0.10.0 からは、組み込みで バックアップ と リストア の機能が追加されています。必要なファイル一式を少しの操作でひとつのフォルダにまとめてくれるうえ、リストアも全自動なので、バックアップやリストアを行いたいときは基本的にはこれの利用がおすすめです。使い方は別エントリで紹介しています。\nAWX Operator による AWX のバックアップとリストア | kurokobo.com 何らかの理由で AWX Operator の機能を使わずにすべてを手作業で行いたい場合は、\nデプロイに利用したファイル一式 証明書と秘密鍵を含む 二つの PV のまるごとの中身 /data/postgres と /data/projects 自動生成されたシークレットキー \u0026lt;インスタンス名\u0026gt;-secret-key で自動作成されるシークレット kubectl -n awx get secret awx-secret-key -o yaml \u0026gt; secret_key.yaml などで保存しておく AWX 内で /etc/tower/SECRET_KEY として利用され、DB 内の機微情報（Credential など）の暗号化や複合化に使われる の保持を考えます。これらが維持できれば、極論、K3s をアンインストールしても、元のファイルを元のパスに置いて作り直せば同じ環境に戻せます。\n# シークレットキーの保存 kubectl -n awx get secret awx-secret-key -o yaml \u0026gt; secret_key.yaml # 環境全体のアンインストール /usr/local/bin/k3s-uninstall.sh # 環境全体の再インストール・再デプロイ curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644 kubectl apply -f https://raw.githubusercontent.com/ansible/awx-operator/0.10.0/deploy/awx-operator.yaml kubectl create namespace awx kubectl apply -f secret_key.yaml kubectl apply -k . PostgreSQL の PV は、静止点が取りにくければ、素直に pg_dump でもよいですね。\nkubectl exec awx-postgres-0 -n awx -- sh -c \u0026#39;pg_dump -c --if-exists -U $POSTGRES_USER\u0026#39; \u0026gt; dump.sql リストアするには、awx リソースの replicas を `` にして、PostgreSQL だけを残して AWX を止めてからがよさそうです。\nkubectl patch awx awx -n awx -p \u0026#39;{\u0026#34;spec\u0026#34;: {\u0026#34;replicas\u0026#34;: 0}}\u0026#39; --type=merge kubectl exec -i awx-postgres-0 -n awx -- sh -c \u0026#39;psql -U $POSTGRES_USER\u0026#39; \u0026lt; dump.sql kubectl patch awx awx -n awx -p \u0026#39;{\u0026#34;spec\u0026#34;: {\u0026#34;replicas\u0026#34;: 1}}\u0026#39; --type=merge まとめ 軽量 Kubernetes ディストリビューションの K3s を使って AWX をホストする方法を紹介しました。パスワードを固定してしまうのは少し乱暴ではありますが、その分少しだけ取り回しはしやすくなっています。\n前述の通り、0.10.0 から追加された AWX Operator 組み込みのバックアップとリストアは、別のエントリ で取り扱っています。手作業よりは圧倒的にラクなので、併せて使うと便利です。\n","date":"2021-06-05T10:17:00Z","image":"/archives/3807/img/image-248.jpg","permalink":"/archives/3807/","title":"AWX を AWX Operator でシングルノード K3s にホストする"},{"content":"概要 様々な興味と関心が脱線し続けた結果、OBS で Among Us を生配信するときに便利かもしれないオーバレイ表示を実現するツール、AmongUsOverlay ができました。例えばこんなことができます。\nロビーにいるときだけ、部屋コードを隠す画像を自動で表示 する（ロビー以外では自動で非表示になる） プレイヤの色と名前 を表示し、死亡したプレイヤは死亡した画像に自動で切り替える（ネタバレにならないよう、緊急会議に入ったときに更新される） 選択した ステージに応じたマップを自動で表示 する。ディスカッション中だけは大きなマップに切り替える ゲームの終了後に、その ゲームのまとめ（勝者や時系列のイベント）を表示する 動作イメージは次のとおりです。\nまた、簡単な HTML と CSSで、上記のような画面を自分で簡単に作れるようになっています。例えば、上記のうち、ロビーでだけ表示される画像 は、次のたった一行の HTML で実現できます。\n\u0026lt;img class=\u0026#34;per_state only_lobby\u0026#34; src=\u0026#34;\u0026lt;path/to/image\u0026gt;\u0026#34;\u0026gt; すぐに使えるデモ兼実装例も併せて配布しています。\n仕組み OBS の ブラウザ ソースを使うと、任意の Web ページをオーバレイ表示できます。\nつまり、Among Us のゲームの進行に応じて動的に表示が更新される Web ページ が作れれば、それは OBS でオーバレイ表示できるということです。\nAmongUsOverlay は、まさにそういう Web ページを実現するためのツール です。\n技術的には、\nCaddy でローカル環境に Web ページをホストして ゲームの情報を Among Us 用キャプチャツール AmongUsCapture（自動ミュートボットこと AutoMuteUs でおなじみのアレ）で取得して AmongUsCapture の組み込みの API エンドポイントから WebSocket でその情報を受け取って JavaScript で動的に表示を更新する な具合で動きます。\nデモと実装例 冒頭にも書いた例ですが、簡単に紹介です。\n部屋コードを自動で隠す 📁 Automatic Lobby Code Censor\nAmongUsOverlay は、特定のクラスを任意の HTML タグに加えることで、特定のフェイズでだけ表示する要素 を簡単に作れます。これを利用して、ロビーでだけ表示される画像 を作成すると、部屋コードを隠す目的で利用できます。\nプレイヤの生死を表示する 📁 Automatic Player Status\nAmongUsOverlay は、特定の ID とクラスを使って HTML のテンプレートを定義することで、プレイヤの一覧 を簡単に作れます。色と名前だけでなく 生死 も表示できます。なお、緊急会議に入らないと死亡表示には切り替わらないので、ネタバレの心配もありません。\nプレイヤの一覧は、あらかじめ色と表示順を固定できる固定（Fixed）モードと、入室・退室に応じて動的に自動で生成される動的（Dynamic）モードの二種類から選べます。\n固定（Fixed）モード\r動的（Dynamic）モード\rマップを自動表示する 📁 Automatic Map Switcher\nAmongUsOverlay は、特定のクラスを任意の HTML タグに加えることで、特定のフェイズでだけ表示する要素 を簡単に作れます。また、同様に 特定のマップでだけ表示する要素 も簡単に作成できます。\nこれを組み合わせることで、選択したマップに合わせたミニマップを自動表示 し、さらに 緊急会議中だけフルマップに切り替える ような動きを実現できます。\nゲームの結果のまとめ画面を表示する 📁 Automatic Result Screen\nAmongUsOverlay は、特定の ID とクラスを使って HTML を記述することで、ゲームの結果のまとめ（振り返り）画面を作成できます。この画面は、Victory/Defeat の画面で TRY AGAIN か QUIT を押したときに表示 され、一定時間後に自動で非表示 になります。\n結果まとめ画面には、勝者や敗者、時刻のほか、ゲームのタイムライン も表示されます。\n使い方 詳しくは GitHub に載せています が、こちらも簡単に紹介です。\nAmongUsCapture の用意 AmongUsCapture をダウンロードしてインストールし、起動します。左上の歯車アイコンで設定画面を開いて API Server を有効にします。\nAmongUsOverlay の用意 AmongUsOverlay のリリースページ から最新版を ZIP でダウンロードし解凍して、AmongUsOverlay.bat を起動します。自動で Caddy（シングルバイナリの軽量 HTTP サーバ）がダウンロードされて、起動されます。\n動作テスト ブラウザで http://localhost:42080/example-debug/ にアクセスし、テスト画面が出ることを確認します。\nAmong Us を起動して、任意のマップで FREEPLAY に入ります。うまくいけば、次の画像のように自動で更新されるはずです。\nOBS の設定 OBS で ブラウザ ソースをいくつか追加し、動作を試したいデモの URL を入力します。\nデモ URL デモの推奨サイズ 📁 部屋コード隠し http://localhost:42080/example-auto-lobbycode-censor/ 480 x 240 📁 プレイヤ生死表示 http://localhost:42080/example-auto-player-status/ 1500 x 240（10 人まで）、2250 x 240（15 人まで） 📁 マップ表示 http://localhost:42080/example-auto-map-switcher/ 1920 x 1080 📁結果まとめ画面 http://localhost:42080/example-auto-result-screen/ 1280 x 720 ロビーに入ってゲームを遊ぶと、いろいろとごりごり表示が更新されていくはずです。\n二回目以降の OBS の操作 前述の手順で初回の設定と動作確認ができたあと、OBS を終了させて再度起動すると、オーバレイ表示がうまく自動更新されないことがあります。これは、OBS のブラウザソースが必ずしも動的にページを読み込まずに、内部でキャッシュした画像を表示しているだけの場合があるからです。\nこのため、二回目以降 の利用時（すでに作成したブラウザソースをそのまま利用する場合）は、AmongUsOverlay を起動させてから OBS を起動したあと、作成した すべて のブラウザソースのプロパティを開いて、[ 現在のページを再読込 ] をクリックし、明示的にページを更新してください。\nなお、OBS 上のオーバレイ表示には、この操作を行った 以降 に受信したデータ のみ が利用されます。確実なトラッキングのため、この操作は Among Us の起動前（少なくともロビーに入る前）に行うことを推奨します。\n利用上の注意 AmongUsCapture と AmongUsOverlay はどちらもゲーム中は常時起動させておく必要があります ゲーム内でイベントが起きた瞬間に必要なツールがすべて起動していないと、情報の取得漏れが起きます。過去にさかのぼっての情報の取得はできません 各ツールの起動順は次の流れを推奨します。これ以外の順序で起動した場合、ゲームの状態が正常にトラッキングできない場合があります AmongUsCapture AmongUsOverlay OBS ブラウザソースの設定を含む 作成済みブラウザソースを再利用する場合は、前述の [ 現在のページを再読み込み ] を実行 Among Us オーバレイ中のプレイヤの情報がおかしくなったとき（プレイヤがうまく検知されないなど）は、ロビーにある PC で、いちどプレイヤの色を変えて戻してください カスタマイズ方法 デモ用の実装例のカスタマイズのほか、自製も簡単にできるようになっています。\nデモをカスタマイズする それぞれのデモ用のページで、簡単なカスタマイズや設定の方法を紹介しています。\n📁 部屋コード隠し 📁 プレイヤ生死表示 📁 マップ表示 📁 結果まとめ画面 自作する template フォルダを複製して任意の名前に変更（フォルダ名が OBS で使う URL の一部になります）し、中身をいじります。\nHTML と CSS を使う 指定するといろいろな機能が有効になるクラスや ID を用意しています。例えば、\n\u0026lt;span\u0026gt; などに追加すると、ゲームの特定の情報（現在のフェイズ、部屋コード、マップ名など）を自動で挿入してくれるクラス 任意の HTML タグに追加すると、フェイズやマップに応じて表示・非表示を切り替えてくれるクラス プレイヤの一覧や結果まとめ画面のタイムラインを動的に生成するためのテンプレートを定義できるクラスや ID などです。これらとご自身で用意した画像や CSS でのレイアウトを組み合わせるだけでも、相当いろいろなことができるはずです。\n詳しい説明は テンプレート中の index.html のコメント で記載しています。\nJavaScript を使う JavaScript のスキルがあれば、完全にカスタマイズできます。事前に定義済みのハンドラや変数があるので、それらを組み合わせて任意の処理を記述できます。例えば、\n特定のフェイズに入ると呼び出されるハンドラ プレイヤの状態に変化が生じると呼び出されるハンドラ 部屋情報が更新されると呼び出されるハンドラ ゲーム開始や終了で呼び出されるハンドラ プレイヤ情報を常時保持している変数 ゲームの最新状態を常時保持している変数 最後のゲームの結果情報を保持している変数 などです。根本的には、AmongUsOverlay 自体が JavaScript でできているため、既成のハンドラだけでなく、そもそものコア部分に手を入れることも可能です。\n詳しい説明は テンプレート中の js/custom.js のコメント で記載しています。\nまとめ AutoMuteUs の開発からいろいろ脱線して遊んでいくうちに AmongUsCapture の API の存在にたどり着き、この API があるならそういえばアレをああしてこうするとこういうのができるなあ、ちょっとやってみよう、みたいなノリでこうなりました。たのしかったです。\nなお、ぼく自身は生配信をしたこともする予定もなく、実際の使い心地は全然わかりません……。ひょっとしたら信じられないくらい使いにくいかもしれないので、何かご意見があれば適宜お知らせください。\n","date":"2021-05-16T04:57:45Z","image":"/archives/3792/img/amongusoverlay.jpg","permalink":"/archives/3792/","title":"AmongUsOverlay： OBS で Among Us のゲーム状況をリアルタイムにオーバレイ表示する"},{"content":"Among Us でゲームの状況に応じて自動でミュート・アンミュートしてくれる便利なボットこと AutoMuteUs、ほんとうに便利ですよね。\n簡単な使い方であればインタネットの各所に解説記事がたくさんありますが、実は AutoMuteUs には、さらに便利に活用できるいろいろな機能 が用意されています。\n本エントリでは、他の解説記事ではあまり触れられていない機能 ====を中心に、AutoMuteUs をさらに活用するための情報 を紹介します。\nなお、文中で 公式サービス と記載していますが、これは AutoMuteUs 開発者が直々に運営しているサービス というだけの意味であり、Among Us の公式サービス ではない ことは念のため明記しておきます。\nミュート・アンミュートの高速化 ゲーム開始直後やディスカッションの開始・終了時など、同時に多数のプレイヤのミュート・アンミュートが必要なシーン で、実際にミュート・アンミュートされるまでにプレイヤ間でラグ が生じることがあります。\nAutoMuteUs では、ミュートとアンミュートに Discord の API を利用しています。Discord の API には一定時間内に許容される操作の回数の制限（Rate Limits）があり、ざっくり、トークンごとかつサーバごと・チャンネルごと にこの制約を受けます。前述のラグは、この制限によるものです。\nこのラグは、それぞれ次の方法で解消または軽減できます。現在の実装では 8 人以上で遊ぶときに特に制限を受けやすい ため、いずれかの対策を検討するとよいでしょう。\n利用形態 高速化方法 公式サービス（無償またはブロンズ） キャプチャボットによる高速化 公式サービス（シルバー以上） キャプチャボットによる高速化 ワーカボットによる高速化 セルフホスト キャプチャボットによる高速化（5.0.0 以上のみ） ワーカボットによる高速化 キャプチャボットによる高速化 AmongUsCapture は、ボットからの依頼を受けてプレイヤのミュート・アンミュートを手伝う機能を持っています。この機能でミュート・アンミュートの処理を分散することで、Discord の制限を受けにくくできます。\nこの手段は、公式サービスを無償で利用している場合でも有効 な点が魅力です。もちろん、有償サービスの利用者もセルフホストの利用者（5.0.0 以降限定）にも有効です。\n利用するには、公式のドキュメント を参考に、\nDiscord Developer Portal へアクセスし ボットを作成してトークンを取得し 権限（Mute Members と Deafen Members **のみ**でよい）を付与して サーバへ招待する な作業をしたうえで、AmongUsCapture の設定画面でトークンを入力する必要があります。最初だけちょっと手間ですが、いちど設定してしまえばあとは放置でよいので簡単です。\nAutoMuteUs の開発者が、この作業の解説動画も公開していますので、併せて参考にできます。\nなお、セルフホストの場合で、キャプチャのトークン欄にボット側の DISCORD_BOT_TOKEN と同じ値を入れても動作はしますが、Discord からすれば同じトークンでのリクエストになるので、制限を回避する観点では大きな効果は期待できません（厳密には完全に無意味でもないのですが、別のトークンを利用する方が効果は大きいはずです）。\nワーカボットによる高速化 AutoMuteUs Premium の シルバー以上 で、公式ページで Priority Muting Bots と表記されているのがコレです。シルバー以上の利用者は、/premium invite コマンドで、追加のボットの招待リンクを取得でき、サーバに追加でボットを招待するだけで、ミュート・アンミュートが高速化されます。\nセルフホスト の利用者は、追加でボットを作成し、トークンを取得してサーバに招待（権限は Mute Members と Deafen Members のみでよい）した上で、.env ファイルの WORKER_BOT_TOKENS にそのトークンを追記すれば利用できます。WORKER_BOT_TOKENS はカンマ区切りで複数のトークンを設定できるので、ワーカボットを複数用意すればさらに動作を安定させられます。詳細な説明は省きますが、ワーカボット 1 つあたり 7 人まで と考えて数を調整するとよいでしょう。\nなお、環境変数 WORKER_BOT_TOKENS は 5.0.0 以上のみの対応のため、それ以前（2.4.3 など）を利用している方は、代わりに DISCORD_BOT_TOKEN_2 として追記してください。ただし、DISCORD_BOT_TOKEN_2 には複数のトークンの列挙はできないので、ワーカボットの数は 1 つのみに限定されます。\n技術的な補足 技術的には、ミュート・アンミュートの Discord へのリクエストは、以下の優先順位で処理されています。\nワーカボット WORKER_BOT_TOKENS で入力したトークンによる処理 複数のトークンを入力した場合は、すべてのトークンで順次処理を試行 キャプチャボット AUCapture の Settings 画面に入力したトークンによる処理 プライマリボット DISCORD_BOT_TOKEN で入力したトークンによる処理 プライマリボットはそもそも制限を受けやすい（ユーザからのコマンドへの応答やメッセージの投稿などで API リクエストを行わざるを得ない）ため、ミュート・アンミュートの処理に関しては最後の手段として使われます。\nさらに技術的には、Discord の制限とは 別 に、Galactus もワーカボットのトークンごとのミュート・アンミュートリクエストを 5 秒あたり 7 回に制限しています。環境変数でカスタマイズできる箇所ではありますが、Discord 側の制限が変わるわけではないので、この数値をいじるメリットはあまりありません。\nゲーム開始直後のミュート時間の変更 ゲーム開始後、役職が明らかになって即座に声で反応してしまうと、ミュートになっていなくてバレる…… みたいな事故がまれに発生します。\n実は、デフォルトでは、ゲームの開始後は 7 秒待ってからミュート するようになっています。同様に、ディスカッション終了からタスクに戻るときも、デフォルトでは 7 秒待ってからミュート します。/settings delays（.au settings delays）コマンドでこの待ち時間を短くすることで、この事故は予防できます。\nコマンドは、/settings delays start-phase:\u0026lt;遷移前フェイズ名\u0026gt; end-phase:\u0026lt;遷移後フェイズ名\u0026gt; delay:\u0026lt;数値\u0026gt;（.au settings delays \u0026lt;遷移前フェイズ名\u0026gt; \u0026lt;遷移後フェイズ名\u0026gt; \u0026lt;数値\u0026gt;）で、必要なパラメータはそれぞれ次の通りです。\n遷移前フェイズ名、遷移後フェイズ名 LOBBY（lobby）、TASKS（tasks）、DISCUSSION（discussion） のいずれかをそれぞれ指定します 例えば、遷移前フェイズを LOBBY、遷移後フェイズを TASKS と指定すると、ロビーからタスクに遷移したとき、すなわち ゲーム開始時 を表現できます 数値 待ち時間を秒単位で指定します デフォルト値は次の通りです。\n遷移前フェイズ 遷移後フェイズ 待ち時間 補足 LOBBY TASKS 7 ゲームを開始した直後 TASKS LOBBY 1 タスク中にゲームが終了したとき（タスク勝ち、キル勝ちなど） DISCUSSION LOBBY 6 ディスカッションでゲームが終了したとき（追放勝ちなど） DISCUSSION TASKS 7 ディスカッションが終了してタスクに戻るとき 統計や成績、タイムラインの表示 有償の AutoMuteUs Premium、またはセルフホストで 5.0.0 以降を利用している場合、過去に遊んだゲームの統計や成績を表示できます。\nサーバ全体の統計やユーザ別の成績の表示 /stats view guild（.au stats guild）コマンドで、そのサーバの過去の統計情報を確認できます（ギルドとは、Discord ではサーバのことです）。\n勝率（Total Winrate）、役割別の勝率（Crewmate/Imposter Winrate）、メンバの組み合わせでの勝率（Best/Worst Imposter/Crewmate Team）、最初に殺されがちなヒト、最初に特定のヒトに殺されがちなヒト、の情報が得られます。\nまた、/stats view user user:`` \u0026lt;@ユーザ名\u0026gt; ``（.au stats \u0026lt;@ユーザ名\u0026gt;）では、個人単位の成績も表示できます。\nこれらの統計情報画面の表示は、それぞれ次のオプションでカスタマイズできます。\nコマンド 意味 /settings leaderboard-mention use-mention:``` （.au settings leaderboardMention `） 表示に含まれるユーザ名のメンション化の有無を指定する。デフォルトは True で、メンションが飛ぶ。False にするとニックネームだけになり、メンションは飛ばない。 /settings leaderboard-size size: （.au settings leaderboardSize） ランキングに表示する順位を指定する。デフォルトは上図の通り 3 で 3 位までの表示だが、例えば 5 にすると 5 位まで表示されるようになる。 /settings leaderboard-min minimum: （.au settings leaderboardMin ``） あるユーザがランキングに含まれるようになるために必要なゲームの数を指定する。デフォルトは 3 で、そのサーバで 3 回以上ゲームをしないとランキングに載らない。 ゲームのタイムラインの表示 開発途中の機能ではありますが、/stats view match match:\u0026lt;ゲーム ID\u0026gt;（.au stats \u0026lt;ゲーム ID\u0026gt;）を実行すると、そのゲームの時系列が確認できます。\n必要なゲーム ID は、ゲームオーバメッセージ の中で Match ID として確認できますが、デフォルトではそもそもこのメッセージ自体が無効化されています。下図のようなメッセージです。\nこのゲームオーバメッセージを有効化するには、/settings match-summary-duration minutes-duration:\u0026lt;数値\u0026gt;（.au settings matchSummary \u0026lt;数値\u0026gt;）を実行する必要があります。数値には、ゲームオーバメッセージの表示時間（分単位）を指定しますが、個人的なおすすめは -1（無期限）です。1 以上の数値を指定すると、その時間が経過したらそのゲームオーバメッセージは削除されるようになります。\n観戦者のミュート・アンミュート AutoMuteUs は、通常はゲーム内のプレイヤと Discord のアカウントがリンクされているヒトだけを対象にミュート・アンミュートを行うのみで、プレイヤでない観戦者がボイスチャットに混ざっていても、そのヒトはミュート・アンミュートしません。\n有償サービスまたはセルフホストでは、/settings mute-spectators mute:True（.au settings muteSpectators true）を実行すると、同じボイスチャットに参加しているプレイヤでない観戦者 を、死者と同じルールでミュート・アンミュート するようになります。画面配信と組み合わせると便利に利用できますね。元に戻すには、同じコマンドで True でなく False を指定します。\nなお、この機能は観戦者の数に応じて前述の Discord の API 制限を受けやすくなるため、利用には注意が必要です。利用する場合は、同じく前述のキャプチャボットやワーカボットの併用が推奨されます。\nチャットで流れたメッセージの再表示 ボットが投稿してくれるこのメッセージは、チャットで流れてしまいがちです。\nそんなときは、/refresh（.au refresh）を実行すると、新しく投稿しなおしてくれます。\nまた、/settings auto-refresh autorefresh:True（.au settings autoRefresh true）で自動リフレッシュを有効化すると、\nディスカッションの開始時 ゲームの終了時（Try Again か Quit をクリックしてロビーかメニュー画面に戻ったとき） に、自動で /refresh（.au refresh）に相当する処理を行ってくれるようになります。\nミュート・アンミュートの条件の変更 デフォルトでは、例えば、\nロビーでは全員聴こえるし喋れる タスク中は死者のみ聴こえるし喋れる ディスカッション中は全員聴こえるが、喋れるのは生存者だけ な状態ですが、このルールも細かくカスタマイズできます。コマンドは /settings voice-rules deaf-or-muted:\u0026lt;muted/deafened\u0026gt; phase:\u0026lt;フェイズ名\u0026gt; alive:\u0026lt;dead/alive\u0026gt; value:\u0026lt;True/False\u0026gt;（.au settings voiceRules \u0026lt;mute/deaf\u0026gt; \u0026lt;フェイズ名\u0026gt; \u0026lt;dead/alive\u0026gt; \u0026lt;true/false\u0026gt;）で、パラメータはそれぞれ次の通りです。\nmuted/deafened（mute/deaf） muted でマイクミュートの設定を、deafened でスピーカミュートの設定を変更します フェイズ名 ロビーは LOBBY（lobby）、タスク中は TASKS（tasks）、ディスカッションは DISCUSSION（discussion）として、いずれかを指定します dead/alive dead で死者の設定を、alive で生存者の設定を変更します True/False（true/false） muted/deafened で指定した設定について、フェイズ名 で指定したフェイズ中の dead/alive で指定した状態のプレイヤの設定の有効（True）・無効（False）を指定します デフォルトでは、以下のコマンド相当の設定が投入されています。\n設定 フェイズ 生死 デフォルト値 muted LOBBY alive False muted LOBBY dead False muted TASKS alive True muted TASKS deaf False muted DISCUSSION alive False muted DISCUSSION deaf True deafened LOBBY alive False deafened LOBBY deaf False deafened TASKS alive True deafened TASKS deaf False deafened DISCUSSION alive False deafened DISCUSSION deaf False ボット操作のロールによる制限 AutoMuteUs では、デフォルトではボットに対するすべての操作をサーバの全員が行えてしまいますが、次の二つの役割を設定すると、ボットを操作できるメンバを制限できます。人数が多いサーバで何らかの統制が必要な場合には、この機能が便利に使えるかもしれません。\n管理者（admin） ボットに対するすべての操作を実行できる権限を持つユーザ 管理者が一人以上設定されていると、設定の変更（/settings の実行）は管理者しかできなくなる 運用者（operator） ゲームの作成や終了（/new、/end）やポーズ（/pause）、強制アンミュート（/debug unmute-all）、設定変更（/settings）など、ゲームの操作やミュート・アンミュートに関わる処理を実行できる権限を持つロール ただし、管理者が一人以上設定されている場合は、設定変更はできない 設定は、以下のコマンドで行います。\n管理者の指定 /settings admin-user-ids user user:\u0026lt;@ユーザ名\u0026gt;（.au settings adminUserIDs \u0026lt;@ユーザ名\u0026gt;）で管理者を指定 /settings ... の場合は、ユーザ名は 1 ユーザずつ複数回のコマンドで指定する .au ... の場合は、ユーザ名は必要なだけ複数名をスペース区切りで列挙する /settings admin-user-ids clear（.au settings adminUserIDs clear）で管理者設定をリセット /settings admin-user-ids view（.au settings adminUserIDs）で現在の設定を確認 運用者の指定 /settings operator-roles role role:\u0026lt;@ロール名\u0026gt;（.au settings operatorRoles``\u0026lt;@ロール名\u0026gt;）で運用権限を与えるロールを指定 /settings ... の場合は、ロール名は 1 ロールずつ複数回のコマンドで指定する .au ... の場合は、ロール名は必要なだけ複数をスペース区切りで列挙する） /settings operator-roles clear（.au settings operatorRoles clear）で運用者設定をリセット /settings operator-roles view（.au settings operatorRoles）で現在の設定を確認 設定した段階で、必要な権限を持たないユーザはコマンドを実行できなくなります。\n管理者と運用者は必ずしも両方設定する必要はなく、どちらか一方でも構いません。また、サーバのオーナは、管理者と運用者の設定に関わらず、常にすべての操作を実行できます。\nコマンドの簡易表記（6.x 以前のセルフホストのみ） 6.x 以前向けの .au から始まるコマンドは、本エントリでは一貫して完全表記で記載していますが、例えば .au settings は .au s だけ、.au new は .au n だけなど、一部のコマンドは簡易表記に対応しています。簡易表記は、一部はコマンドごとのヘルプで Aliases として記載されています。\nどこにも書かれていない簡易表記も存在しており、挙げるとキリがないので列挙はしませんが、一例ではフェイズ名も簡略化が可能です。例えば次の二つのコマンドは同じ意味です。\n.au settings delays discussion tasks 3 .au s d d t 3 ほかにも、ture は t、false は f、clear は c などちょこちょこあるので、遊べる環境があれば探してみるのもよいでしょう。いちばん確実なのは、ソースコードを読むことです。\nコマンドを .au から別の文字列に変更する（6.x 以前のセルフホストのみ） 6.x 以前でデフォルトで .au になっているコマンドプレフィックスは、任意で別の文字列に変更できます。設定コマンドは次の通りです。\n\u0026lt;現在のプレフィックス\u0026gt; settings commandPrefix \u0026lt;新しいプレフィックス\u0026gt; 例えば、現在のプレフィックスが .au で、これを .mybot に修正したい場合は、次の通りに操作します。\n.au settings commandPrefix .mybot 以降は、例えば .mybot new などで操作できるようになります。\nまた、docker-compose.yml と sample.env で、AUTOMUTEUS_GLOBAL_PREFIX によるデフォルトのプレフィックスの変更に対応しています。ただし、プレフィックスはサーバ単位で保存されているため、途中から変えたい場合には結局は上記コマンドでの操作も併用する必要があります。\nイースターエッグ（6.x 以前のセルフホストのみ） 実はこんなアスキーアートを表示する機能が隠されています。探してみましょう。\n残念ながら 7.x では削除されています。\nメッセージ中のルームコードの非表示 バージョン 6.13.0 から、ボットが投稿するメッセージ中のルームコードを、このように非表示にできるようになりました。\n/settings display-room-code visibility:\u0026lt;always/spoiler/never\u0026gt;（.au settings displayRoomCode \u0026lt;always/spoiler/never\u0026gt;）で変更できます。\nデフォルトは always です。spoiler では Discord のネタバレ機能でコードが隠されるようになり、閲覧にクリックが必要になります。never では完全に秘匿されます。\nマップの画像を差し替える セルフホスト限定 ですが、バージョン 6.15.3 から、/map（.au map）コマンドやゲーム開始時のメッセージに含まれるマップの画像を、自前のものに差し替えられるようになりました。ただし、画像が HTTP でホストされ ていて、かつ、所定のファイル名のルールに従っていて、さらに クエリ文字列 ?raw=true が付与されてもアクセスできる 必要があります。GitHub のリポジトリに PNG ファイルを配置するのがラクそうです。\n設定は、.env にある環境変数 BASE_MAP_URL で画像群のベースになる URL を指定することで行います。内部的なデフォルトは https://github.com/denverquane/automuteus/blob/master/assets/maps/ です。\n配置が必要な画像のファイル名は次の通りです。設定すると、マップの画像の URL として、BASE_MAP_URL と、ファイル名と、?raw=true を連結した文字列が利用されるようになります。\nマップ（バージョン） ファイル名 利用される URL The Skeld（通常） the_skeld.png BASE_MAP_URL + the_skeld.png``?raw=true The Skeld（詳細） the_skeld_detailed.png BASE_MAP_URL + the_skeld_detailed.png?raw=true` MIRA HQ（通常） mira_hq.png BASE_MAP_URL + mira_hq.png?raw=true` MIRA HQ（詳細） mira_hq_detailed.png BASE_MAP_URL + mira_hq_detailed.png?raw=true` Polus（通常） polus.png BASE_MAP_URL + polus.png?raw=true` Polus（詳細） polus_detailed.png BASE_MAP_URL + polus_detailed.png?raw=true` The Airship（通常） airship.png BASE_MAP_URL + airship.png?raw=true` The Airship（詳細） airship_detailed.png BASE_MAP_URL + airship_detailed.png?raw=true` 公式サービスの負荷状況の確認 公式サービスを無償で利用している場合、高負荷時にゲームを開始できない制約を受けます（有償サービスまたはセルフホストでの利用時は、この制約はありません）。\nグローバルで 150 ゲーム がこの制限の閾値ですが、最近は日本時間で 21:00 頃から 24:00 頃 が特に高負荷で、この閾値を恒常的に超えた状態になっています。\n次のグラフは、ある一日の公式サービスの負荷状況です。最近は連日だいたいこんな感じで、ピーク時は 230 近くまで増えることもあるようです。盛況ですね。\n公式サービスの負荷状況（その時点でグローバルで実行されているゲームの数）は、公式サービスのボットに対して /info を実行すれば、結果の中の Active Games 欄で確認できます。\n上図の例だと 153 なので、150 を超えているため、公式サービスの無償ユーザは .au new ができない状態です。\nプレミアム機能のフリートライアル 公式サービスでは、2022 年 5 月のアップデートで、AutoMuteUs Premium の一部の機能を無料で試用できる ようになりました。試用すると、ブロンズ相当の以下の機能が有効化 されます。\n150 ゲームの制限の撤廃（/new） ボットの負荷が高い時間帯でも、いつでもゲームを開始できるようになります 統計や成績の詳細な表示（/stats） 前述の 統計や成績、タイムラインの表示 で紹介した、詳細な統計情報や成績が確認できるようになります 一部の高度な設定の有効化（/settings） /settings list で表示される結果のうち、Premium Settings に区分される設定を確認・変更できるようになります 試用期間中に変更した設定は、試用期間後も変更されたままになります ただし、試用には以下の制約や条件 があります。\n試用は Discord のアカウント単位で有効化 されます サーバ単位ではありません 試用中のユーザ本人がコマンドを実行したときだけ機能します 試用は Discord のアカウント==ごとに 1 回のみ== で、有効期間は 12 時間 です top.gg（Discord のボットの投票サイト）で、試用したい Discord のアカウントで AutoMuteUs に投票 する必要があります 少しややこしいのは、試用が Discord のアカウント単位 である点です。例えば、無償プランを使っているあるサーバに A さんと B さんがいて、そのうち A さんのみが試用を開始 した場合、/new や /stats などのコマンドは、A さんが実行したときだけプレミアム扱い されます。同じコマンドを B さんが実行しても、プレミアム扱いはされません。したがって、例えばそのサーバで 150 ゲームの制限を回避してゲームを開始するには、/new を A さんに実行してもらう などの工夫が必要になります。\nまた、A さんが複数のサーバに所属している場合は、どのサーバでも A さんが実行したコマンドはプレミアム扱い されます（サーバですでに AutoMuteUs Premium を利用中の場合はそちらが優先されます）。\n試用を開始するには、/premium info の出力の Vote for the Bot on top.gg のリンクか、top.gg の AutoMuteUs のページ で、試用を有効化したい Discord のアカウントでログインして投票するだけです（投票する前に広告を見る必要があります）。\ntop.gg での投票は時間を空ければ複数回できますが、何回投票しても AutoMuteUs の試用は Discord のアカウントあたり 1 回のみ で、有効期間は 投票から 12 時間 です。\n投票後、有効期間中に /premium info を実行すると、試用中である旨が表示されます。\n一緒に遊ぶプレイヤが多い場合は、日ごとに交代で試用を有効化するなど工夫すれば、便利に使えるかもしれません。\nデータベース内データのダウンロード 7.3 から、データベース内のデータを CSV ファイルとしてダウンロードできる機能が追加されました。ゴールド以上の公式サービス、またはセルフホストで利用できます。次のエントリで詳しく紹介しています。\nAutoMuteUs 7.3 の新機能： データベース内のデータのダウンロード API からの情報の取得 通常の用途では出番がない機能ですが、8.0 で RESTful API のエンドポイントが少しだけ用意されました。\nautomuteus コンテナが 5000 番ポート（デフォルト値）で待ち受けており、最新の docker-compose.yml ではこれがコンテナホストの 80 番（デフォルト値、環境変数 API_PORT で変更可）にバインドされています。\n本ブログでは詳しくは取り上げませんが、詳細は起動後に /swagger/index.html で公開される Swagger UI で確認できます（Swagger UI で表示されるベース URL は環境変数 API_SERVER_URL で修正できます）。試す場合は 8.1.2 以降がおすすめです。\n抜粋ですが、エンドポイントには以下のようなものがあります。\n/bot/info ボットの基礎情報（バージョン、登録ギルド数、アクティブゲーム数、累積ゲーム数など） /bot/commands コマンドの一覧 /game/state（要認証） 現在実行中のゲームの詳細情報 /guild/settings（要認証） ギルドの設定情報 /open/link キャプチャソフトの起動 認証は、ユーザ名は固定で admin、パスワードはデフォルトで automuteus です。パスワードは環境変数 API_ADMIN_PASS で変更できます。\nおわりに AutoMuteUs のちょっとマイナかもしれない機能群を紹介しました。ほかにも、\nタスク中に死んだら直ちにアンミュートする機能（/settings unmute-dead） ミュート・アンミュートを一時的にしなくする機能（/pause） 鬼ごっこや初参加者の練習時などに 保存されているプレイヤ名の確認や削除（/debug view user、/debug clear user） 設定のリセット（/settings reset） 全設定の現在の値の表示（/settings show） 統計収集の拒否（/privacy） など実にいろいろな機能があり、ヘルプをつぶさに観察するのも楽しいかもしれません。\nAutoMuteUs 関連おすすめエントリ AutoMuteUs 7.3 の新機能： データベース内のデータのダウンロード AutoMuteUs 7.0 のリリース： スラッシュコマンド対応などいろいろ AutoMuteUs のよくある質問と回答とかいろいろ Among Us 用ボット AutoMuteUs のあまり知られていない便利な機能 ","date":"2021-04-25T04:32:11Z","image":"/archives/3730/img/image-246.jpg","permalink":"/archives/3730/","title":"Among Us 用ボット AutoMuteUs のあまり知られていない便利な機能"},{"content":"はじめに NUC 8 のオンボード NIC は、ESXi 7.0 U1 から、組み込みのドライバではうまく認識されなくなっています。このため、NUC 8 の ESXi を 7.0U1 以降にアップグレードするには、これまでは対象のドライバ ne1000 だけ旧バージョン（7.0b 世代）にダウングレードする必要がありました。この目的で、以前のエントリ では、特定のドライバだけダウングレードしたカスタムイメージプロファイルの作成と、それを利用したアップグレードを紹介しています。\nが、先日、VMware Flings でこの問題を解決できる新しいドライバがリリースされました。\nCommunity Networking Driver for ESXi | VMware Flings New Community Networking Driver for ESXi Fling このドライバは、ne1000 ではなく別の新しい e1000-community（と igc-community）として認識されるため、組み込みの ne1000 ドライバと一切競合せず、共存できる 点がポイントです。NIC は ne1000 でなく e1000-community を使って動作するようになるため、ne1000 のバージョンは気にする必要がなくなり、ダウングレードが不要 になります。\n適用方法 従来通り個別に導入したりカスタムイメージプロファイルへ組み込んだりできるだけでなく、vLCM にコンポーネントとしてインポートすれば、イメージ管理でも利用できます。\nコマンドラインでの適用 リリースページの Instructions に記載 されている方法です。データストアなどに配置して esxcli software vib install する、従来の方法です。詳細は割愛します。\nイメージプロファイルへの組み込み イメージプロファイルに組み込んで適用することも可能です。イメージプロファイルの作成には、以前のエントリ でも登場した vCenter Server の Image Builder のほか、PowerCLI も使えます。\nイメージプロファイルができれば、手動で適用する以外にも、以前のエントリ のように vLCM のベースライン管理でも取り扱えるようになります。\nvSphere ESXi Image Builder Installation and Usage これも従来通りなので、詳細は割愛します。\nvLCM イメージへの組み込み 今回はこの方法を採用しました。\n特定のドライバを ESXi に導入したい場合で、以前のエントリ のようにそれが 既存のドライバの置き換え（アップグレードまたはダウングレード）である場合には、vLCM のイメージ管理は使えませんでした。\n一方で、今回のドライバは、先述の通り別の新しい e1000-community（と igc-community）として認識されるため、従来の ne1000 には何ら依存関係がなく、競合しません。ne1000 のバージョンを気にする必要がそもそもなくなるので、単純な 新しいドライバの追加、つまり、vLCM イメージへのコンポーネントの追加 として取り扱えます。\nこのためには、まずは Lifecycle Manager の画面で、アクション \u0026gt; 更新のインポート（ACTIONS \u0026gt; Import Updates）でダウンロードした ZIP ファイルをインポートします。\nこれにより、vLCM イメージの構成要素である コンポーネント として取り込まれます。結果は デポのイメージ \u0026gt; コンポーネント（Image Depot \u0026gt; Component）で 独立型コンポーネント（Independent components）の一つとして確認できます（リリース日でソートすると探しやすくなります）。\n最後に、ホストクラスタに紐づける vLCM イメージを新規に作成（または既存イメージを編集）して、コンポーネントの一つとして今回のドライバを追加します。\nこれで、クラスタの ESXi ホストにとって あるべき状態（Desired State や Desired Image と表現されます）として、指定した ESXi のベースイメージに今回のドライバが追加された状態が定義されました。あとはコンプライアンスを確認して、クラスタの ESXi ホストをこのイメージに準拠するよう修正すれば、実際にドライバが導入されます。\n修正が完了すると、物理アダプタのドライバが e1000-community になっている様子が確認できます。\nおわりに これで、泣く泣くベースライン管理に戻していた ESXi のバージョン管理手段を、再びイメージ管理に戻せました。ne1000 に一切競合しないで共存できるようにデザインされている点がきれいですね。別の視点では、実際にコンポーネントを追加する手順を実践できたのもよかったです。\n7.0 が出たての頃は、vLCM のイメージ管理にも種々の制約がありましたが、U1 でのアップデートをはじめ、今後も発展が期待できます。今後一般化するであろう新しい機能をハードウェアの（いわば相性の）問題で使えないのは残念だったので、今回のドライバはうれしいリリースでした。\n","date":"2021-02-22T09:16:31Z","image":"/archives/3705/img/image-234.png","permalink":"/archives/3705/","title":"新しいドライバで NUC 8 のオンボード NIC を ESXi 7.0U1 で使えるようにする"},{"content":"はじめに プライベート CA が ACME プロトコルを喋れれば、ACME クライアントを使った証明書の発行や更新を気軽に試せて便利です。\nもちろん正規の証明書は得られませんが、その代わり、インタネットにポートを露出することなく HTTP-01 チャレンジや TLS-ALPN-01 チャレンジが行えます。公開認証局のリソース負荷やレート制限、CT ログなども気にする必要はなくなりますし、上位の DNS で応答をコントロールすれば、実在しない TLD の証明書も発行できます。\nこのエントリでは、気軽に実現できる方法のうち、実際に試した次の三パタンを紹介します。\nSmallstep の step-ca を使う Caddy の組み込みの acme_server を使う Let\u0026rsquo;s Encrypt の Boulder または Pebble を使う ACME クライアントを試す目的では、ふたつめの Caddy 案が個人的にはいちばん手軽で小回りが効く印象でした。\nSmallstep の step-ca を使う 素直にドキュメントに従えば動いてくれます。コンテナ環境で動かしたい場合もチュートリアルがあるので安心です。なお、初期状態では ACME プロトコルでの証明書発行機能は無効化されているため、利用には明示的な宣言が必要です（三つめのリンクを参照）。\nInstalling open source step-ca — Smallstep — Build Big Docker TLS private certificate authority in a container — Smallstep — Build Big ACME challenge and client connections with step-ca — Smallstep — Build Big 初期設定から起動まで 生の Docker ではなく Docker Compose で制御したかったので、最小限の Compose ファイルを作りました。設定はすべて /home/step の下にできるので、永続化はここだけ考えればよさそうです。\nversion: \u0026#34;3\u0026#34; services: ca: image: smallstep/step-ca:0.15.6 restart: always ports: - \u0026#34;9000:9000\u0026#34; volumes: - \u0026#34;ca-data:/home/step\u0026#34; volumes: ca-data: CA としての設定は CLI ベースで、環境変数などでの流し込みはできなそうでした。このため、必要な設定ファイル群がボリューム内に置かれた状態にするところまでは、手で操作していきます。\n~/step-ca$ docker-compose run --rm ca sh Creating network \u0026#34;step-ca_default\u0026#34; with the default driver Creating volume \u0026#34;step-ca_ca-data\u0026#34; with default driver ~ $ step ca init ? What would you like to name your new PKI? (e.g. Smallstep): EXAMPLE.COM ? What DNS names or IP addresses would you like to add to your new CA? (e.g. ca.smallstep.com[,1.1.1.1,etc.]): ca.example.com ? What address will your new CA listen at? (e.g. :443): :9000 ? What would you like to name the first provisioner for your new CA? (e.g. you@smallstep.com): jwk ? What do you want your password to be? [leave empty and we\u0026#39;ll generate one]: ? Password: b)_.u\u0026lt;[R7fd(uT_zit1s1MNd9\u0026lt;H(JlVr Generating root certificate... all done! Generating intermediate certificate... all done! ? Root certificate: /home/step/certs/root_ca.crt ? Root private key: /home/step/secrets/root_ca_key ? Root fingerprint: ef17bfc5dffda4d23257330b9e5b6aa75727c9663b32f59666c679ce3ec489c7 ? Intermediate certificate: /home/step/certs/intermediate_ca.crt ? Intermediate private key: /home/step/secrets/intermediate_ca_key ? Database folder: /home/step/db ? Default configuration: /home/step/config/defaults.json ? Certificate Authority configuration: /home/step/config/ca.json Your PKI is ready to go. To generate certificates for individual services see \u0026#39;step help ca\u0026#39;. FEEDBACK The step utility is not instrumented for usage statistics. It does not phone home. But your feedback is extremely valuable. Any information you can provide regarding how you’re using `step` helps. Please send us a sentence or two, good or bad: feedback@smallstep.com or join https://github.com/smallstep/certificates/discussions. ~ $ echo \u0026#39;b)_.u\u0026lt;[R7fd(uT_zit1s1MNd9\u0026lt;H(JlVr\u0026#39; \u0026gt; /home/step/secrets/password ~ $ step ca provisioner add acme --type ACME Success! Your `step-ca` config has been updated. To pick up the new configuration SIGHUP (kill -1 \u0026lt;pid\u0026gt;) or restart the step-ca process. ~ $ exit この段階で、必要なすべてのファイルができています。CA のルート証明書も自己署名証明書として作られ、中間証明書も生成されます。\nあとは起動するだけです。\n~/step-ca$ docker-compose up -d Creating step-ca_ca_1 ... done これで、以下の URL が ACME ディレクトリ URL として使えるようになります。\nhttps://ca.example.com:9000/acme/acme/directory ただし、このエンドポイントの HTTPS それ自体に、この CA のルート証明書（自己署名証明書）で署名された中間証明書が使われているので、このままではだいたいの（きちんとした）ACME クライアントではエンドポイントへのアクセスの時点で失敗します。\nこのため、CA のルート証明書をクライアント側で明示的に信用させるか、またはクライアントが（暗黙的に）参照している証明書ストアに登録する必要があります（システムレベルで登録することは推奨しません）。今回の場合、初期設定で生成された CA のルート証明書がコンテナ内の /home/step/certs/root_ca.crt に保存されているので、これを docker cp などで取り出して使います。\ndocker cp step-ca_ca_1:/home/step/certs/root_ca.crt . ほかにも、コンテナ内には秘密鍵や中間証明書類も次のパスで生成されているので、必要に応じて利用できます。\n/home/step/certs/root_ca.crt /home/step/secrets/root_ca_key /home/step/certs/intermediate_ca.crt /home/step/secrets/intermediate_ca_key このうち秘密鍵は、パスフレーズ（step ca init 中に入力または生成したパスワード）で保護されています。のっぴきならない事情があってパスフレーズを取り除きたい場合は、ssh-keygen や openssl コマンドで取り除けます。\nssh-keygen -p -m pem -f \u0026lt;鍵ファイル\u0026gt; openssl ec -in \u0026lt;鍵ファイル\u0026gt; -out \u0026lt;鍵ファイル\u0026gt; 既存証明書の利用 CA で利用されるルート証明書として、初期化時に自動生成されるモノではなく、別途用意したモノを利用したい場合にも対応可能です。ドキュメントではいくつかの方法が紹介されています。\nIntermediate CA, use existing PKI \u0026amp; open source step-ca — Smallstep — Build Big 例えば、上記ドキュメントの最も簡単な手順（step ca init に --root と --key を渡す）に従う場合は、今回であれば次のようになります。ここでは、Compose ファイルは前述のモノのまま変更せず、一時的にルート証明書と秘密鍵を適当なパスにマウントして利用しています。\n~/step-ca$ docker-compose run --rm -v $PWD/root_ca.crt:/tmp/root_ca.crt -v $PWD/root_ca_key:/tmp/root_ca_key ca sh Creating network \u0026#34;step-ca_default\u0026#34; with the default driver Creating volume \u0026#34;step-ca_ca-data\u0026#34; with default driver ~ $ step ca init --root=/tmp/root_ca.crt --key=/tmp/root_ca_key ? What would you like to name your new PKI? (e.g. Smallstep): EXAMPLE.COM ? What DNS names or IP addresses would you like to add to your new CA? (e.g. ca.smallstep.com[,1.1.1.1,etc.]): ca.example.com ? What address will your new CA listen at? (e.g. :443): :9000 ? What would you like to name the first provisioner for your new CA? (e.g. you@smallstep.com): jwk ? What do you want your password to be? [leave empty and we\u0026#39;ll generate one]: ? Password: 6~24(,i{um\u0026#39;3.RFd`2ol\u0026#39;k$,L2\u0026#39;2T\u0026gt;K8 ... 秘密鍵にパスフレーズが設定されている場合は、途中で聴かれます。後続のパスワードファイルの作成や ACME プロトコルの追加方法は通常通りなので省略しています。この方法では、中間証明書は再生成されます。\n中間証明書を含めて既存のものを使いたい場合は、先のドキュメントの 2 番めの手順に従って、特に工夫せずに step ca init してから、関連ファイルを置き換えるかマウントしてしまうのがラクそうです。\n動作確認 例えば Caddy の Automatic HTTPS を使う場合であれば、Caddyfile の ca と ca_root で、ACME ディレクトリ URL と CA のルート証明書を指定できます。\ntls (Caddyfile directive) — Caddy Documentation www.example.com { respond \u0026#34;Hello World!!\u0026#34; tls { ca https://ca.example.com:9000/acme/acme/directory ca_root /etc/caddy/root.crt } } Caddy では、DNS-01 チャレンジ用の設定がされていない場合、既定で HTTP-01 チャレンジか TLS-ALPN-01 チャレンジのどちらかがランダムで採用され、失敗時は他方にフォールバックします。いずれにせよ、上位の DNS でドメイン名の実 IP アドレスを適切にごまかしてやれば、任意のドメインの証明書が発行できます。\n例えば、上位の DNS でエントリを以下のように登録しておきます。\nCaddy から見た ca.example.com がプライベート CA を向くように プライベート CA から見た www.example.com が Caddy を向くように そのうえで上記の Caddyfile を利用すれば、実在しないドメイン www.example.com の証明書を取得できます。次のログは、TLS-ALPN-01 チャレンジが実行された様子です。\ncaddy_1 | {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:1612702530.6507704,\u0026#34;logger\u0026#34;:\u0026#34;tls.issuance.acme.acme_client\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;trying to solve challenge\u0026#34;,\u0026#34;identifier\u0026#34;:\u0026#34;www.example.com\u0026#34;,\u0026#34;challenge_type\u0026#34;:\u0026#34;tls-alpn-01\u0026#34;,\u0026#34;ca\u0026#34;:\u0026#34;https://ca.example.com:9000/acme/acme/directory\u0026#34;} caddy_1 | {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:1612702530.6692169,\u0026#34;logger\u0026#34;:\u0026#34;tls\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;served key authentication certificate\u0026#34;,\u0026#34;server_name\u0026#34;:\u0026#34;www.example.com\u0026#34;,\u0026#34;challenge\u0026#34;:\u0026#34;tls-alpn-01\u0026#34;,\u0026#34;remote\u0026#34;:\u0026#34;192.168.160.1:53614\u0026#34;,\u0026#34;distributed\u0026#34;:false} caddy_1 | {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:1612702530.9263132,\u0026#34;logger\u0026#34;:\u0026#34;tls.issuance.acme.acme_client\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;validations succeeded; finalizing order\u0026#34;,\u0026#34;order\u0026#34;:\u0026#34;https://ca.example.com:9000/acme/acme/order/xZCKSlnqGMB0V2YPWPUKNYEWiLvxHDHo\u0026#34;} caddy_1 | {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:1612702530.934317,\u0026#34;logger\u0026#34;:\u0026#34;tls.issuance.acme.acme_client\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;successfully downloaded available certificate chains\u0026#34;,\u0026#34;count\u0026#34;:1,\u0026#34;first_url\u0026#34;:\u0026#34;https://ca.example.com:9000/acme/acme/certificate/ATdtrb5V2GuwyMuBKcd8j5XMIgAFZCoO\u0026#34;} caddy_1 | {\u0026#34;level\u0026#34;:\u0026#34;info\u0026#34;,\u0026#34;ts\u0026#34;:1612702530.9358096,\u0026#34;logger\u0026#34;:\u0026#34;tls.obtain\u0026#34;,\u0026#34;msg\u0026#34;:\u0026#34;certificate obtained successfully\u0026#34;,\u0026#34;identifier\u0026#34;:\u0026#34;www.example.com\u0026#34;} Certbot でも、Certbot が動作する環境（コンテナ内など）の証明書ストアにルート CA の証明書を登録しておけば、証明書が発行できます。\n# certbot certonly --standalone --server https://ca.example.com:9000/acme/acme/directory -d www.example.com -m admin@example.com --agree-tos Saving debug log to /var/log/letsencrypt/letsencrypt.log Plugins selected: Authenticator standalone, Installer None Obtaining a new certificate Performing the following challenges: http-01 challenge for www.example.com Waiting for verification... Cleaning up challenges IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/www.example.com/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/www.example.com/privkey.pem Your cert will expire on 2021-02-14. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run \u0026#34;certbot renew\u0026#34; Caddy の組み込みの acme_server を使う Caddy は HTTP サーバやリバースプロキシとしての用途が一般的ですが、組み込みで ACME プロトコルで会話できる CA の機能を持っています。正確には、前述の Smallstep の step-ca そのものが内包されていて、Caddy で自己署名証明書を使う場合にデフォルトでこの CA 機能が利用されています。\nこの Caddy の組み込み CA 機能は、明示的に Caddyfile や API で指定すれば、外部からも利用できる状態になります。\nacme_server (Caddyfile directive) — Caddy Documentation 初期設定から起動まで Caddyfile に acme_server を追加すれば機能します。隅から隅まで自己署名証明書で完結させる場合は、例えばこれだけで充分です。\nca.example.com:9000 { tls internal acme_server } あとはこれを読むように Caddy を起動させれば、ACME プロトコルに対応した CA として利用できます。Compose ファイルではこのようになります。\nversion: \u0026#34;3\u0026#34; services: caddy: image: caddy:2.3.0 ports: - 9000:9000 volumes: - ./Caddyfile:/etc/caddy/Caddyfile - caddy-data:/data - caddy-config:/config volumes: caddy-data: caddy-config: この場合、ACME エンドポイントは次の URL です。\nhttps://ca.example.com:9000/acme/local/directory 生成された証明書類はコンテナ内では次のパスにあります。\n/data/caddy/pki/authorities/local/root.crt /data/caddy/pki/authorities/local/root.key /data/caddy/pki/authorities/local/intermediate.crt /data/caddy/pki/authorities/local/intermediate.key この方法では、ACME エンドポイントの HTTPS にもこの CA のルート証明書で署名された中間証明書が使われます。先の step-ca の例と同様、クライアント側でルート証明書を明示して信用させる工夫が必要になるでしょう。\nなお、tls internal ではなくまっとうに Let\u0026rsquo;s Encrypt で証明書を発行するように構成（方法は割愛しますが Caddy の通常のお作法通りです）すれば、ACME エンドポイントの HTTPS には正規の証明書が使われるようになるため、クライアント側は何も気にせずに証明書を発行できるようになります（CA としてのルート証明書とは別物なので、当たり前ですが発行される証明書は自己署名ルート証明書に連なります）。\n既存証明書の利用 CA としてのルート証明書に既存のものを使わせたい場合は、上記の 4 ファイルを上記通りのパスとファイル名でバインドマウントする方法がいちばん手軽そうです。この場合、Caddyfile は先の例と同じで問題ありません。\n... volumes: - ./Caddyfile:/etc/caddy/Caddyfile - ./certs/ca.example.com/root_ca.crt:/data/caddy/pki/authorities/local/root.crt - ./certs/ca.example.com/root_ca_key:/data/caddy/pki/authorities/local/root.key - ./certs/ca.example.com/intermediate_ca.crt:/data/caddy/pki/authorities/local/intermediate.crt - ./certs/ca.example.com/intermediate_ca_key:/data/caddy/pki/authorities/local/intermediate.key - caddy-data:/data - caddy-config:/config ... 別のパスの証明書を使わせたい場合は、Caddyfile では表現できない部分なので、JSON フォーマットで流し込む必要があります。具体的には、pki アプリケーションとして次のように記述できます。\n... \u0026#34;pki\u0026#34;: { \u0026#34;certificate_authorities\u0026#34;: { \u0026#34;local\u0026#34;: { \u0026#34;root\u0026#34;: { \u0026#34;certificate\u0026#34;: \u0026#34;/etc/caddy/certs/root.crt\u0026#34;, \u0026#34;private_key\u0026#34;: \u0026#34;/etc/caddy/certs/root.key\u0026#34; }, \u0026#34;intermediate\u0026#34;: { \u0026#34;certificate\u0026#34;: \u0026#34;/etc/caddy/certs/intermediate.crt\u0026#34;, \u0026#34;private_key\u0026#34;: \u0026#34;/etc/caddy/certs/intermediate.key\u0026#34; } } } } ... この設定は、管理用 API を使って渡すほかに、起動時（caddy run 時）に --config \u0026lt;JSON ファイルのパス\u0026gt; を指定しても渡せます。\n動作確認 動き出してしまえば、最初の step-ca の例とまったく同じ使い心地です。省略。\nLet\u0026rsquo;s Encrypt の Boulder または Pebble を使う Let\u0026rsquo;s Encrypt で実際に動いている CA の実装が Boulder で、それのテスト用の簡易版が Pebble です。どちらも GitHub で公開されています。\nletsencrypt/boulder: An ACME-based certificate authority, written in Go. letsencrypt/pebble: A miniature version of Boulder, Pebble is a small RFC 8555 ACME test server not suited for a production certificate authority. Boulder は実際に Let\u0026rsquo;s Encrypt で使われているだけあって、別途 RDBMS を必要とする重厚なモノです。一方 Pebble は、ACME クライアントの開発やテストで使うことを目的に提供されている簡易版です。自動テストへの組み込みも想定されており、テスト用のコマンドやライブラリ、フェイク DNS 機能などがいろいろ詰まった Challenge Test Server も併せて提供されています。\nここでは、Pebble をミニマム構成で動かします。\n初期設定から起動まで テスト用だけあって、少し変わった実装です。\n例えば、ACME エンドポイントのポートは 14000 番がデフォルトで、HTTP-01 チャレンジの検証では発行先の 5002 番ポートを見に行きます。TLS-ALPN-01 チャレンジでは 5001 番です。\n設定は JSON で変えられるので、ファイルとして用意し読ませることにします。\n{ \u0026#34;pebble\u0026#34;: { \u0026#34;listenAddress\u0026#34;: \u0026#34;0.0.0.0:14000\u0026#34;, \u0026#34;managementListenAddress\u0026#34;: \u0026#34;0.0.0.0:15000\u0026#34;, \u0026#34;certificate\u0026#34;: \u0026#34;/test/certs/localhost/cert.pem\u0026#34;, \u0026#34;privateKey\u0026#34;: \u0026#34;/test/certs/localhost/key.pem\u0026#34;, \u0026#34;httpPort\u0026#34;: 80, \u0026#34;tlsPort\u0026#34;: 443 } } Compose ファイルは次のような内容です。\nversion: \u0026#39;3\u0026#39; services: pebble: image: letsencrypt/pebble:v2.3.1 command: pebble -config /test/config/pebble-config.json ports: - 14000:14000 - 15000:15000 volumes: - ./config.json:/test/config/pebble-config.json 起動すると、次のエンドポイントで利用できます。\nhttps://pebble:14000/dir なお、このエンドポイントの HTTPS で使われる証明書（と鍵）は 公開 されており、これを署名している CA の証明書（と鍵）も 公開 されています。クライアント側ではこの証明書を信用する必要がありますが、鍵が公開されていて偽装が容易なため、テスト用の一時的な環境以外では信用させない方が安全です。\nまた、HTTPS の証明書が localhost と 127.0.0.1 と pebble 用のため、URL がこのいずれかでないとそれはそれでクライアント側の検証でハネられます。別ホストや別コンテナで動かす場合は、hosts ファイルや DNS で工夫が必要です。\nHTTPS の証明書ではなく、CA としてのルート証明書と中間証明書は、起動するたびに再生成されます。管理用ポート（デフォルトは 15000 番）から HTTP でダウンロードできるようになっています。\nhttps://localhost:15000/roots/0 https://localhost:15000/root-keys/0 https://localhost:15000/intermediates/0 https://localhost:15000/intermediate-keys/0 既存証明書の利用 前述の通り、CA としての証明書や鍵は起動毎に代わる仕様です。根本的に Pebble はテスト目的の実装であり、そもそも永続的な利用を想定していないため、自前のルート証明書に置き換える手段は公式には用意されていなさそうです。\n動作確認 動いてしまえば、使い方は最初の step-ca のそれと変わりません。\nただし、設定ファイルで httpPort や tlsPort を変えていないと、HTTP-01 チャレンジや TLS-ALPN-01 チャレンジの検証でアクセスする先が 5002 番や 5001 番ポートになるので、その点は注意です。\n# certbot certonly --standalone --server https://pebble:14000/dir -d www.example.com -m admin@example.com --agree-tos Saving debug log to /var/log/letsencrypt/letsencrypt.log Plugins selected: Authenticator standalone, Installer None Obtaining a new certificate Performing the following challenges: http-01 challenge for www.example.com Waiting for verification... Cleaning up challenges IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/www.example.com/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/www.example.com/privkey.pem Your cert will expire on 2026-02-13. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run \u0026#34;certbot renew\u0026#34; おわりに ACME プロトコルで会話できるプライベート CA の立て方を 3 パタン紹介しました。個人的には、ACME クライアントを気軽に試す目的では、Caddy の組み込みの機能が設定要らずで簡単な印象です。\n先の Caddy の Azure DNS 用モジュール の作成の際も、Let\u0026rsquo;s Encrypt にクエリを飛ばしすぎるのが（ステージング環境といえども）ためらわれたので、こうしたプライベート CA がほんのりと役立ちました。\nお楽しみください。\n","date":"2021-02-13T15:08:27Z","image":"/archives/3669/img/image-221.png","permalink":"/archives/3669/","title":"ACME プロトコルで会話できるプライベート CA を立てる"},{"content":"作ったもの Caddy の Automatic HTTPS で、DNS-01 チャレンジ に Azure DNS を利用するためのモジュールをリリースしました。\nModule dns.providers.azure - Caddy Documentation Download Caddy caddy-dns/azure: Caddy module: dns.providers.azure 経緯 Caddy は、Apache HTTP Server や Nginx のような Web サーバの実装のひとつです。特徴的な機能はいくつかあり、その一つに、ドメイン名を指定して起動するだけで Let\u0026rsquo;s Encrypt を使った証明書の発行や更新を全自動で勝手にやってくれる Automatic HTTPS があります。\nこの証明書の発行の際、デフォルトでは HTTP-01 チャレンジか TLS-ALPN-01 チャレンジが行われますが、多少のカスタマイズで DNS-01 チャレンジも利用 できます。外部に公開していないサイトでも正規の証明書が利用できるほか、公開しているサイトでも 80 番ポートや 443 番ポートを閉塞できるなど、種々のメリットがあります。\n一方で、残念ながら この機能が利用できる DNS プロバイダは現時点であまり豊富ではなく、例えば、今回使いたかった Azure DNS は未対応でした（go-acme/lego のラッパはある ものの、Deprecated 扱いなのであまり推奨されません）。\n対応済みプロバイダには Amazon Route 53 などもあるので、使いたいゾーンだけそちらに委任するのがもちろんいちばんラクです。が、本家に PR を送ってレビュしてもらう 流れなど今まであまり経験がなく、ちょっとおもしろそうだったので、自分で作ることにしました。野良モジュールのままで終わらずに、採用してもらえてよかったです。英語はよくわかりません。\n使い方 公式の Wiki で詳しく説明されていますが、xcaddy を使ってビルドするのが簡単です。リリースページ から（もしくは go get して）xcaddy のバイナリを持ってきて、--with でモジュールを渡します。\nxcaddy build --with github.com/caddy-dns/azure@v0.1.0 これでモジュールが組み込まれた Caddy のバイナリ caddy ができあがります。コンテナで使いたい場合は Docker Hub の説明が詳しい ですが、以下のような Dockerfile でビルドするだけです。\nFROM caddy:2.3.0-builder AS builder RUN xcaddy build v2.3.0 \\ --with github.com/caddy-dns/azure@v0.1.0 FROM caddy:2.3.0 COPY --from=builder /usr/bin/caddy /usr/bin/caddy あとは起動して Caddyfile か API 経由で設定を投げ込んでやれば、指定したドメインの証明書を取得するべく Let\u0026rsquo;s Encrypt と Azure DNS を使って DNS-01 チャレンジが始まります。完了すると、正規の証明書とともに HTTPS でホストされます。\nhoge.example.com { tls hoge@example.com { dns azure { tenant_id {$AZURE_TENANT_ID} client_id {$AZURE_CLIENT_ID} client_secret {$AZURE_CLIENT_SECRET} subscription_id {$AZURE_SUBSCRIPTION_ID} resource_group_name {$AZURE_RESOURCE_GROUP_NAME} } } respond \u0026#34;Hello, world!\u0026#34; } 簡単ですね。Azure への認証は今のところ Client Credential のみです。\n余談： 証明書の透明性 若干本筋から逸れますが、Let\u0026rsquo;s Encrypt に限らず、認証局から取得した証明書のドメイン名は自動的に公開される 仕様なので、その点は認識が必要です。つまり、秘密にしたくてこっそり用意したサブドメインであっても、証明書を取得した時点で、認証局がその FQDN に対して証明書を発行した事実は公開 されます。\nこれはいわゆる 証明書の透明性、Certificate Transparency（CT）と呼ばれる仕組みで、主にドメインの所有者やサイトの運営者などが不正な証明書の発行を監視・検証できるようにするためのものです。\nCertificate Transparency（証明書の透明性）| DigiCert ウェブ上での HTTPS 暗号化 – Google 透明性レポート 気にしていないと意外と知らない部分かもしれませんが、CT の公開ログを検索できる次のようなサイトも存在しており、ドメイン名を入力すると、そのドメイン下で過去に証明書が発行された FQDN を簡単に調べられます。\ncrt.sh | Certificate Search 第三者から監視・検証できるようにすることで透明性が得られる（とされる）一方で、ドメイン名から FQDN がわかるだけでなく、探し方を工夫すると共通のサブドメインを持つ世界中の FQDN がわかってしまうこともあって、サブドメインの存在を秘匿できずに悪意ある第三者にとっての情報源になってしまう懸念もあり、当然いろいろな議論はあるようです。\nだからといって一利用者が何かをできるわけでもないのですが、少なくとも、現状はそういう仕様であることは認識しておくとよいですね、というお話でした。\n余談は以上です。\n","date":"2021-01-28T13:40:26Z","image":"/archives/3643/img/image-231.png","permalink":"/archives/3643/","title":"Caddy の Azure DNS 連携用モジュールをリリースした"},{"content":"はじめに Among Us のゲームの進行に応じて Discord 上の各参加者のミュート・アンミュートを自動制御してくれるとても便利なボット、AutoMuteUs を初めて触りました。\nAutoMuteUs 動作にはボットのインスタンスを立てる必要があり、公開インスタンスが満員だったため、その場で自前サーバでのセルフホスト構成を作ったのですが、Docker でホストできることもあって、動かすだけならとても簡単です。Discord でのボット操作を一手に引き受けられるのであれば、インタネット上で公開する必要すらなく、LAN 内に配置するだけで済みます。\nが、自分が居ないときでも仲間内で共同利用できるようにしたい場合、どうしてもボットのインスタンスはインタネット上でホストする必要があり、であれば少しでもセキュアにしたい感触があります。\nそんなわけで、平文で行われている一部の HTTP や WebSocket の通信を、Let\u0026rsquo;s Encrypt の SSL 証明書とリバースプロキシ（Nginx）で暗号化したので、実装例の紹介です。\n追記： Caddy でも実装 しました。エントリ末尾の追記部分 で紹介しています。\n通信と登場人物 通信が少し複雑なので、登場人物と併せて簡単に整理します。デフォルト状態で普通にホストすると、こんな感じになります。\nAmongUsCapture 実行中の Steam 版 Among Us のメモリ（たぶん）を横から覗いて、ゲームの状態を監視し続けるアプリケーション 監視結果（ゲームの状態）は AutoMuteUs の galactus に随時投げる ボット経由での Discord の操作も担うことがある（たぶん） AutoMuteUs Docker ホスト上で動作するボットのインスタンス ゲームの状態を AmongUsCapture から受け取り、 Discord の操作（参加者のミュートやアンミュート）を担う（galactus） Discord でのユーザインタラクションやメッセージ操作を担う（amongusdiscord） ほか、galactus と amongusdiscord の連携用に Redis、統計情報の保存などのために PostgreSQL も動作する 課題 AmongUsCapture と galactus 間の通信が、平文の HTTP や WebSocket で行われています。\nこのため、AutoMuteUs を下図のようにインタネット上でホストした場合、インタネット上に非暗号化トラフィックが流れることになり、経路上での盗聴に対して脆弱になります。誤情報を流し込むこともできてしまいそうですね。\n実際にパケットを覗くと、WebSocket のペイロードが露出していることがわかります。\nなお、AutoMuteUs を LAN 内でホストする（ゲームの実行端末で Docker を動かすか、または仮想マシンなどで Docker ホストを用意する）場合は、この通信も LAN 内で閉じることになるので、ほとんどの場合は気にしなくても大丈夫でしょう。\n対策 galactus の手前に Nginx でリバースプロキシを構成して、経路を HTTPS/WSS 化させます。\nこの時の SSL 証明書には、自己署名証明書ではなく、Let\u0026rsquo;s Encrypt から発行された正規の証明書を利用します。\n実装 実装していきます。\n方針 大げさな実装にはしたくなかったので、\nAutoMuteUs 用の Compose ファイルのカスタマイズ AutoMuteUs 用の .env ファイルのカスタマイズ だけで完結するようにしました。\nつまり、Nginx 用の設定ファイルの用意や ACME クライアントの手動構成などは不要です。この目的で、既成の nginx-proxy と letsencrypt-nginx-proxy-companion を組み込みます。\nできたもの 最新のものは Gist に載せました。\nAutoMuteUs over SSL: All-in-One Docker Compose file with Nginx/Caddy reverse proxy with Let\u0026rsquo;s Encrypt SSL certificate 前提 AutoMuteUs 用のサブドメインを保有していること そのサブドメインが、AutoMuteUs ホストのグローバル IP アドレスに解決できること AutoMuteUs ホストで必要なポートがブロックされていないこと Let\u0026rsquo;s Encrypt の HTTP-01 チャレンジで利用するポート（80/tcp） リバースプロキシの待ち受けに利用するポート（後述の環境変数 NGINXPROXY_EXTERNAL_PORT で指定） .env ファイル ファイルは Gist に載せています。環境変数を 5 つ追加しています。\nNGINXPROXY_EXTERNAL_PORT リバースプロキシが外部で待ち受けるポートを指定します。例では 8443 です。このポートが AmongUsCapture の通信先になります。 NGINXPROXY_TAG nginx-proxy のコンテナイメージ のタグです。GitHub のリポジトリ のタグと一緒です。 NGINXPROXY_COMPANION_TAG letsencrypt-nginx-proxy-companion のコンテナイメージ のタグです。GitHub のリポジトリ のタグと一緒です。 LETSENCRYPT_HOST、LETSENCRYPT_EMAIL Let\u0026rsquo;s Encrypt で証明書を発行するドメイン名と、更新通知を受け取るメールアドレスです。 また、既存の環境変数群は次のように工夫します。\nGALACTUS_HOST HTTPS で記述し、ポート番号は前述の NGINXPROXY_EXTERNAL_PORT と一致させます。 GALACTUS_EXTERNAL_PORT galactus の待ち受けポート番号なので、つまり、リバースプロキシが転送する先のポート番号でもあります。例ではデフォルトの 8123 としています。 docker-compose.yml ファイル ファイルは Gist に載せています。\nnginx-proxy と nginx-proxy-letsencrypt を追加し、その動作に必要なもろもろを修正しています。設定は環境変数ファイルから読み込まれるため、修正は不要です。\n効果 .au new したあとにボットから届く DM のリンクを踏むと、AmongUsCapture が galactus に HTTPS と WSS でつなぐようになります。\nパケットの中も暗号化されました。\n補足 今回は、Google Cloud Platform（GCP）の Google Computing Engine（GCE）で、f1-micro な Container-Optimized OS インスタンスを作成しました。\n静的外部 IP アドレスをひとつ付与 SSH のポート番号をデフォルトから変更 Docker Compose のインストール ファイアウォールルールの作成と割り当て をしています。ドメインは手持ちのもので、DNS サーバには Azure DNS を使っています。\n一点だけ、この nginx-proxy を使う方式の気に入らないところは、Let\u0026rsquo;s Encrypt の HTTP-01 チャレンジのため 80 番ポートを閉じられないところです。Let\u0026rsquo;s Encrypt を使う場合、個人的に普段は DNS-01 チャレンジを好んでいますが、letsencrypt-nginx-proxy-companion は DNS-01 チャレンジに対応していないので、今回は妥協しています（もちろん、Certbot などを別に構成すれば DNS-01 チャレンジも利用可能です）。\nHTTP-01 チャレンジの完了後は、nginx-proxy は 80 番へのアクセスには 301 を返して HTTPS に誘導するようになりはするので、NGINXPROXY_EXTERNAL_PORT を 443 以外にしておけば AutoMuteUs の存在が過度に露出することにはなりませんが、それでもポートスキャンなどで Nginx まではたどり着けてしまうのがちょっと微妙ですね。\nなお、80 番ポートは HTTP-01 チャレンジの発生時（≒リバースプロキシの起動直後と期限が近付いてからの更新時）だけ開放されていればよいので、不測の再起動や更新忘れなどへの備えを犠牲にして（つまりサービスレベルを下げて）よければ、普段は閉じておくのも手です（閉じています）。\n追記： Caddy での実装 ACME クライアントがバンドルされた HTTP サーバの実装、ともいえる Caddy でのリバースプロキシの実装例を先の Gist に追記しました。nginx-proxy と違って DNS-01 チャレンジにも（そこそこ気軽に）対応できるので、80 番ポートを完全に閉塞したままでも（またはそもそもインタネットからの到達性がないサーバでも）証明書の発行・更新ができるのが利点です。\nAutoMuteUs over SSL: All-in-One Docker Compose file with Nginx/Caddy reverse proxy with Let\u0026rsquo;s Encrypt SSL certificate この Gist では、Caddy での Let\u0026rsquo;s Encrypt の証明書発行に nginx-proxy と同様 HTTP-01 チャレンジを利用する場合の例と、併せて Azure DNS を題材に DNS-01 チャレンジを利用する場合の例も紹介しています。\nCaddy は DNS-01 チャレンジ用のプラグイン があまり豊富でない（lego のラッパ は Deprecated）ので、今回はおもしろがって Azure DNS 用のプラグインを自製して使っています。\n","date":"2021-01-13T15:58:33Z","image":"/archives/3629/img/image-226.png","permalink":"/archives/3629/","title":"Among Us の便利ボット AutoMuteUs の通信をリバースプロキシ（Nginx/Caddy）と Let’s Encrypt で暗号化する"},{"content":"はじめに 前回のエントリ で、AfterShokz のフラグシップモデルである Aeropex の音質を紹介しました。その中では、骨伝導であることそれ自体や防水・防塵のメリットは非常に大きい一方で、特に マイクの音質については厳しさがある 旨の記載をしていました。\n根本的には Bluetooth のプロファイル（HFP）の仕様の限界があるので、有線のマイクと同等の高音質にはどうやってもできない（サンプリングレートが 16 kHz なので波形は 8 kHz で頭打ちする）のは仕方がないのですが、それにしても常用は厳しいと言わざるを得ない音質でした。\nマイクの性能を重視するのであれば、Aeropex ではなく通話に特化した OpenComm の発売を待った方が良さそうです。 AfterShokz Aeropex の快適さと、音質の正直なところ | kurokobo.com\r今回、この中でも言及している、通話に特化したモデルであるところの AfterShokz OpenComm を入手したので、主にマイクの性能の観点で、Aeropex と比較しました。\n結果として、OpenComm のマイクは、Bluetooth 自体の仕様上の音質の限界はあるものの、Aeropex のそれとは比較にならないくらい充分常用できるレベルで音がよく、総じて満足できそうでした。\nOpenComm で通話をする 聴感上、たしかに通話に特化したモデルだけあって、Aeropex のマイクとはまったく異なり、一言でいえば 充分に日常的に使えそう でした。もちろん、有線接続のヘッドセットやきちんとしたマイクと比較してしまうと、やはり高音域の欠損に起因する “こもり” ははっきりと認識できてしまいますが、会話に支障が生じたり、相手に音質面で強いストレスを与えたりする心配はほぼないだろうと思える範囲内です。音質に敏感でなければそこまでは気にならない程度でしょう。\n前回のエントリ と同じ方法でマイクの性能を確認すると、Bluetooth の限界（8 kHz までしか使えない）の中ではあるものの、充分な再現性が得られると言えそうです。原音（フリーのナレーション素材）の周波数特性と比較した結果がこちら。\nAeropex と比較すると、劇的な向上が見られます。通話用を謳うだけのことはあります。\nもちろん、有線接続のヘッドセットのマイクと比較してしまうと、8 kHz 以上が失われる点はやはり気になってしまいます。\n前回のエントリでも少しだけ言及していますが、この 8 kHz の壁は、Bluetooth の通話用プロファイル（HFP）に起因しているため、Bluetooth 接続のヘッドセットである以上は製品を問わず不可避です。HFP（のバージョン 1.6 以降）で使えるコーデックはサンプリングレートが 16,000 Hz であり、理論上、8 kHz 以下の音しか伝達できません（同じ HFP でも、バージョン 1.5 まででは 4 kHz までとさらに劣化します）。\n実際のところ、通話で必要な音質は、極論すれば発話した内容が言葉として認識できる程度に伝達できさえすればよいわけで、人間の声の周波数特性からは、この観点では 8 kHz 以下のみしか使えなくても実影響はほとんどありません。が、8 kHz 以上の成分は音声の全体的な明瞭感（特に子音）に寄与している部分も大きく、同じ音声でも 8 kHz 以上がないと若干こもって聴こえてしまいます。\nそんなわけで、Bluetooth 接続である以上、ある程度マイクからの音がこもるのはどの製品でも不可避 で、有線接続のマイクと比較すると音質面では絶対的に不利 です。\nそういう制約が事実としてありはしますが、とはいえこの OpenComm のマイクは、通話特化モデルだけあって、日常のオンラインミーティングなどでも支障なく使えるものだと言えそうです。\nOpenComm で音楽を聴く 音楽を再生したときの音質は、前回のエントリで言及している Aeropex のそれと大きくは変わらず、低音域はかなり減衰しますので、骨伝導方式でない同価格帯のイヤホンやヘッドホンと比較してしまうとお世辞にもよいとはいえません。とはいえもちろん、これも前回のエントリで書いたことではありますが、骨伝導方式である時点でそもそも音楽の再生能力 “のみ” で語るべき製品ではないので、このあたりわかって使うべき部分ですね。\nなお、Aeropex とまったく一緒ではなく、低音側も高音側も、Aeropex よりも若干再生できる（聴こえる）レンジは広そうでした。中音域もやや穏やかになっていて、音のキャラクタも異なるようです。\nまとめ マイクの音質は 100 点ではありませんが、普段使いの気軽なヘッドセットとしては充分満足できる製品でした。有線接続のヘッドセットはそれはそれで取り回しが煩雑でもあったので、少しだけ音質を犠牲にしてフットワークの軽さを手に入れられたと考えれば、とてもよい買い物だったと思っています（Aeropex では犠牲が大きすぎてそもそもその選択ができなかったわけで……）。\n骨伝導方式は、音を聴くために必要なコストが圧倒的に低く、音を気軽に聴く目的ではほんとうに快適です。もちろん、例えば音に集中したいときやきちんとした音質が必要なとき、外音を遮断したいときなどには、Aeropex であれ OpenComm であれ圧倒的に不向きではありますが、そこは最初から織り込むべきで、つまり目的やシーンに応じて別の適切なヘッドホンやイヤホンやマイクやヘッドセットを使い分ければよいだけです。その使い分けの選択肢の一つとして Aeropex や OpenComm を持っておくことは、個人的にはかなりアリなのでした。\nもともと、テレワークでオンラインミーティングが繰り返されるようになり、毎回ケーブリングを気にするのに辟易し始めたのがワイヤレスヘッドセットを探し始めたきっかけでした。ワイヤレスヘッドセットというカテゴリでは Jabra や Plantronics（現 Poly）ほか各社選択肢がありますが、\n持ち歩きたい（≒かさばるオーバヘッド型は避けたい） 両耳で聴きたい おまけ機能ではなくまともなマイクが付いていてほしい 耳を塞がないでいたい な要件を満たせる製品はもはや OpenComm くらいしかなく、今回の購入に至った経緯があります。つまり、正直にいえば消去法的な買い方ではあったわけですが、結果的には、よい選択肢を日常に加えられて満足です。\nなお、OpenComm は、Aeropex よりも防水・防塵性能が低く、マイクブームが稼働部品でもあるので、取り扱いがデリケートな感があります。OpenComm はオンラインミーティング用にして、Aeropex はトレーニングやながら聴き用にするなど、適宜使い分けていきたいです。\n","date":"2021-01-04T15:03:53Z","image":"/archives/3611/img/DSC06585.jpg","permalink":"/archives/3611/","title":"AfterShokz OpenComm のマイク性能を Aeropex と比較する"},{"content":"はじめに 本ブログでもちょこちょこ話題にしてきた EdgeX Foundry ですが、最後のエントリからもう半年以上経過してしまいました。\nEdgeX Foundry は、半年に一度のペースで新しいバージョンがリリースされます。ぼくが 日本語のハンズオンラボガイド を最初に公開した 4 月時点ではまだ Fuji（1.1） が最新でしたが、5 月に Geneva（1.2）がリリースされ、11 月には Hanoi（1.3）がリリースされています。\nというわけで、リリースノートを中心に、Geneva をいまさらおさらいした後、Hanoi の更新情報も見ていきましょう。\nGeneva 2020 年 5 月 13 日にリリースされました。リリースノートは公式 Wiki で公開されています。また、LF Edge のブログでも紹介があります。\nGeneva - EdgeX Wiki - EdgeX Confluence EdgeX Foundry\u0026rsquo;s Geneva Release and Plans for Hanoi - LF Edge 新機能系をざっくり見ていきます。\nDynamic Device Provisioning の追加 これは、デバイスサービスが定期的（または REST API のでトリガを契機）にデバイスのスキャン（Device Discovery）を実行し、応答があったデバイスを動的に EdgeX Foundry 配下に登録できる機能です。Dynamic Device Discovery、Automatic Provisioning などとも表記されます。\nGeneva では、これを実装するためのフレームワークが SDK に追加されました。SDK に含まれるサンプルデバイスサービス（device-simple）でごく簡単な実装例が示されています。\nDiscovery - EdgeX Foundry Documentation device-sdk-go/example at master · edgexfoundry/device-sdk-go 自分でも 5 月末くらいに既成の MQTT デバイスサービスに機能を追加で実装して試してみていました。Slack の DM で質問も来たので、ついでとばかりにガイドを書いて公開済みです。\nedgex-geneva-auto-discovery-mqtt-sample.md kurokobo/device-mqtt-go: Owner: Device WG kurokobo/docker-device-mqtt-go - Docker Hub けっこうおもしろいデモができそうな感じでした。\nエクスポートのバッチ処理の追加 アプリケーションサービスのエクスポートをバッチ処理できるようになりました。具体的には、SDK への機能追加と併せて、パイプラインで使える組み込みのファンクションが追加されています。\nBuilt-In Transforms/Functions - EdgeX Foundry Documentation App Service Configurable - EdgeX Foundry Documentation データのエクスポートを、データの到着都度ではなく、一定の時間やデータ個数ごとにまとめて行えるようになり、オフライン環境や通信の安定しない環境でもエクスポートしやすくなるとされています。\nただし、このバッチでのデータの溜め込みはメモリ内で行われるため、データ量やバッチの間隔によっては使用メモリの肥大化やパフォーマンス劣化を招く可能性があります。利用の際は、実環境でテストしたうえでパラメータを調整するなど、ある程度のチューニングは必要になるかもしれません。\nこれも動作は確認済みです。\nMQTTS や HTTPS でのエクスポート機能の追加 生の MQTT や HTTPS ではこれまでもエクスポートできましたが、アプリケーションサービスで、それぞれ MQTTS、HTTPS でのエクスポートがサポートされました。\n実際に試した範囲では、Geneva の段階だと バグ があり、動作させるには Vault なしにはひと工夫必要 でしたが、クライアント証明書を使ったセキュアなエクスポートができるようになっています。\nApp Service Configurable - EdgeX Foundry Documentation App Service Configurable - EdgeX Foundry Documentation 一部機能の廃止や変更 MongoDB が非推奨になり、標準のデータベースが Redis に変更されました。非推奨なだけなので利用は可能で、Geneva では MongoDB を使う Compose ファイルも用意はされていますが、次の Hanoi ではなくなっています。\nまた、サポートサービス層の要素のひとつ、ロギングサービスも非推奨になりました。これも Geneva ではまだ利用は可能ですが、MongoDB 同様、Hanoi では Compose ファイルからは除かれています。一般論としてコンテナのログは標準出力に吐くべきものであり、標準出力に吐かれたログの扱いはコンテナプラットフォーム側の責任といえるので、その考え方に寄せた（独自で実装するべきものではないと判断された）ということでしょう。\nさらに、ルールエンジンが Java で記述された従来の Drools ベースのものからサードパーティの Kuiper に変更されています。EdgeX Foundry 独自のカスタマイズなしにネイティブの Kuiper と連携できるようで、疎結合でよいですね。\nKuiper Rules Engine - EdgeX Foundry Documentation kuiper/edgex_rule_engine_command.md at master · emqx/kuiper kuiper/edgex_rule_engine_tutorial.md at master · emqx/kuiper このほか、エクスポートサービス層（今ではアプリケーションサービス層ですが）に存在していた Export Client サービスと Export Distro サービスも廃止になっています。\nその他 比較的大きめの（とぼくが思った）ものをピックアップします。\nコアサービスとアプリケーションサービスの間のメッセージングにはこれまで ZeroMQ が使われていましたが、MQTT と Redis Streams のサポートが追加されました。 REST デバイスサービス、ONVIF 対応カメラ用のデバイスサービス、C 言語版の BACnet デバイスサービスが追加されました。 デバイスサービスやコアサービスで扱えるデータとして、配列がサポートされました。複数のセンサの値をまとめて扱えるようになります。 Reading オブジェクトにデータ型を表すフィールドが追加されました。Value Descriptor なしである程度はデータを適切にハンドリングできるようになります。 各種サービスに起動時用の設定ファイルを配置する役割のシーディングサービスが廃止されました。各サービスは起動に必要な処理をそれぞれ自分自身で行うようになります。 アプリケーションサービスのパイプラインに JsonLogic ベースのルールを表記できるようになりました。ごく簡単なルールエンジンとして利用できます。 デフォルトでは、API ゲートウェイ（Kong）以外はポートをホストの外部に公開せず、127.0.0.1 のみで待ち受けるようになりました。 アプリケーションサービスが任意の秘密情報を Vault に追加できるようになりました。また、すべてのサービスが秘密情報を Vault から取得できるようになりました。 アプリケーションサービスの実装例が追加で公開されました（一部は GA 前です）。 API のドキュメントが Swagger Hub での公開に変更されました。 UI の機能が強化されました（ただし UI そのものは開発者向けです）。 これらの純粋な機能面での変更だけでなく、開発プロセスや品質保証面でも様々な変更や改善、強化が行われているようです。\nHanoi Hanoi は出たてほやほやで、2020 年 11 月 20 日にリリースされました。1.x 系の最後のマイナリリースになるようです。\nHanoi - EdgeX Wiki - EdgeX Confluence The EdgeX Foundry Hanoi Release - LF Edge EdgeX Foundry, the Leading IoT Open Source Framework, Simplifies Deployment with the Latest Hanoi Release, New Use Cases and Ecosystem Resources - LF Edge こちらも新機能系をざっくり見ていきます。\nCLI のリリース 開発が進められていた CLI が GA になりました。cURL や Postman などで REST API コールを手作りしなくても、デバイスの追加などに必要な操作はおおむね実行できるようになっています。\nただし、EdgeX Foundry が動作しているホスト上での利用が想定されており、リモートインスタンスへの操作はサポートされていません。\nedgexfoundry/edgex-cli エクスポートデータのタグ付けのサポート EdgeX Foundry が集めたデータは、アプリケーションサービスを使ってさらに上位の分析基盤やクラウドサービスなどにエクスポートできます。Hanoi では、複数の EdgeX Foundry インスタンスがあるときに、データのエクスポート元のインスタンスをエクスポート先が判断できるようにするために、タグ付けがサポートされたようです。\n具体的には、アプリケーションサービスのパイプラインで使える組み込みのファンクションにタグ付け用のものが追加されています。\nBuilt-In Transforms/Functions - EdgeX Foundry Documentation App Service Configurable - EdgeX Foundry Documentation Compose ファイルの編集性の向上 これまでのリリースでは、デバイスサービスやアプリケーションサービスにカスタマイズが必要な場合、Docker Compose で利用する巨大な Compose ファイルの手動編集が避けられませんでした。\nHanoi では、Compose ファイルは make コマンドでビルドする方式に変更されています。コアサービス群、デバイスサービスごと、セキュリティサービス群、など部品化された Compose ファイルが用意されており、make コマンドの引数で組み合わせやアーキテクチャを指定する形です。\n個人的には、確かに長大なファイルをいじくりまわす必要性を減らせたメリットはあるものの、触った感触として、なにかをカスタマイズしたいときに編集すべきファイルの判断や make に渡すべき引数の検討など考えるべきことが逆に増えてしまっている部分もあるので、デメリットもある感触です。\ndeveloper-scripts/compose-builder at master · edgexfoundry/developer-scripts デバイスサービスの分散デプロイの実現 これまでは、EdgeX Foundry を構成するすべてのマイクロサービスがひとつのホストの中にある状態でした（正確には複数ホストでも分散はできたものの、通信の秘匿などの面で懸念が残る状態でした）が、Hanoi ではデバイスサービスをリモートホストに配置する構成を取りやすくなっています。\n具体的には、コアサービスが動作するホストとデバイスサービスが動作するホストの間で SSH トンネルを構成するための手法が公開されています。また、Docker Swarm でオーバレイネットワークを構成して疎通させる PoC も公開されました。\nedgex-examples/security/remote_devices at master · edgexfoundry/edgex-examples 自動パフォーマンステストの追加 EdgeX Foundry のインスタンスに対して、管理下に配置できるデバイス数など、自動的にパフォーマンスをテストできる仕組みが用意されました。\n現状ではまだ Modbus デバイスサービスのみですが、今後、デバイス種別も範囲も強化されていくことが予想されます。\nedgex-taf/modbus-scalability-test.md at master · edgexfoundry/edgex-taf V2 API の実験的リリース 正式には次の Ireland で採用される予定のため、Hanoi ではベータ扱いですが、V2 API が含まれています。各種機能追加が図られているようです。\nedgex-go/openapi/v2 at master · edgexfoundry/edgex-go その他 このほか、比較的大きめの（とぼくが思った）ものをピックアップします。\n同じ LF Edge 傘下で、主に産業向け IoT のプラットフォームである Fledge に対して、EdgeX Foundry からエクスポートするサンプルが提供されました。次のリリースでは、逆に EdgeX Foundry に Fledge からデータを取り込む手段が提供されるようです。 シークレットストア（Vault）のマスターキーをハードウェア（TPM など）の支援の下で保護できるようになりました（参考）。 GPIO や UART、LLRP、CoAP など新しいデバイスサービスが公開されました。 Snap 用の実装の維持管理が Canonical に移譲されました。 コアデータサービスをバイパスして、メッセージバスを利用してデバイスサービスからアプリケーションサービスに直接データを送れるようになりました。 実装例を集める edgex-examples リポジトリができました。 Ireland 来年の春にリリースされる予定の Ireland は、メジャリリースす。つまり、Edinburgh（1.0）から続いていた 1.x 系が終わり、Ireland は 2.0 になる予定です。\nAPI も V2 に置き換えられ、デバイスサービスなどの認定プログラムも開始されるようです。\nおわりに Geneva と Hanoi をざっくり振り返りました。だんだん個人で追いかけるのも限度が出てきますね。引き続き趣味の範囲でゆるゆるとやっていきます。\nEdgeX Foundry 関連エントリ 冬休みの自由研究： EdgeX Foundry (1) 概要 冬休みの自由研究： EdgeX Foundry (2) 導入とデータの確認 冬休みの自由研究： EdgeX Foundry (3) エクスポートサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (4) MQTT デバイスサービスの追加 冬休みの自由研究： EdgeX Foundry (5) ルールエンジンによる自動制御 冬休みの自由研究： EdgeX Foundry (6) アプリケーションサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (7) Kubernetes 上で Fuji リリースを動かす EdgeX Foundry ハンズオンラボガイド公開 EdgeX Foundry： Geneva から Hanoi へ ","date":"2020-12-12T11:49:27Z","image":"/archives/3599/img/DSC06315.jpg","permalink":"/archives/3599/","title":"EdgeX Foundry： Geneva から Hanoi へ"},{"content":"はじめに AfterShokz の Aeropex を買いました。世に出ているコンシューマ向け骨伝導イヤホンというとだいたい AfterShokz か BoCo あたりになるかと思いますが、Aeropex はそのうち AfterShokz の現時点でのフラグシップモデルです。\n骨伝導であること、耳を塞がないこと、装着感の軽さなどから、音を聴くためのコストを圧倒的に下げられて総じて非常に快適 で、防水・防塵性能も満足のいくものでしたが、音質は絶対評価すると正直だいぶ厳しい 感触でした。\nもちろん、根本的には 骨伝導方式は絶対的な音質を重視して買うものではない ですし、本質的な価値は骨伝導であることそのもの にこそあるので、そもそも音質 “のみ” でこの製品を語るべきではありません。また当然ながら、骨伝導だと肉や骨の影響も個人で異なりますし、そうでなくても音質の感じ方や捉え方にはそもそも個人差があります。\nが、特に音質については、インタネット上のレビュ記事では過大評価感も否めませんでした。そこでこのエントリでは、あくまで個人的な（ぼくの耳に依存した）ものではありますが、音質面での正直な感想をちょろっと書いていきます。\n前提 で、そうはいってもそもそもおまえの耳って信用できるの？ といわれるとあまり自信もないわけですが、ぼく自身は、\n普段は HD650、IE80、MDR-CD900ST、WF-SP900、HD598CS など 1 ～ 5 万円あたりのレンジのヘッドホン・イヤホンと ValveX-SE（真空管ヘッドホンアンプ）を愛用している 15 年くらい、PA、生音系楽器の録音、マスタリングなどを趣味でかじっている ようなヒトです。\nAeropex で音楽を聴く 聴感上、明らかに 低音域がかなり減衰 します。\n周波数特性は公式には 20 Hz ～ 20 kHz までとされていますが、テストトーンで試すと、ぼくの耳では、通常のヘッドホン・イヤホンで 16 ～ 16 kHz まで聴き取れる環境 で、Aeropex だとだいたい 50 ～ 13.5 kHz の聴き取りが限度でした。可聴範囲が狭いだけでなく、音が低くなるにつれて音量の減衰 を感じます。\nごく簡単なセットアップではありますが、録音用の別のマイクに密着させた状態でホワイトノイズを再生 したときの周波数特性が次の通りです。\nバイノーラルマイクでもなく専用の設備もない手作業でのアナログな測定ではありますが、IE80 に比較して 1.6 kHz 以下の減衰が見て取れます。また、骨伝導ならではですが、空気ではなく骨の振動を媒介して聴くことになるため、さらに骨や骨に至る肉、脂肪の物的特性による減衰や吸収の影響もあるはずです。\n結果として、低音域が薄くてもあまり気にならない類の音楽を BGM やラジオのように聴き流すのであれば気軽に使えますが、鑑賞・視聴目的でじっくり聴いたり、重低音があってこそのジャンルの再生に使おうとすると、ペコペコになってしまってあまり楽しめない、というのが正直な感想です。\nAeropex で通話をする Aeropex にはマイクも搭載されており、通話目的での利用も謳われています。\nが、聴感上は、素直に表現すると ものすごくモコモコな音 です。通話相手にストレスを与えかねないレベルなので、昨今のテレワークでのオンラインミーティングなどでの利用は厳しい感触でした。\nマイクは、フリーのナレーション素材 をスピーカで再生し、それをマイクで拾わせて録音して確認しました。\n比較対象とした HD598CS のマイクでは、原音に対してそこそこ近い特性を描けたのに対し、\nAeropex のマイクでは、低音域の持ち上がりに対して中音域以上の落ち込みが激しく、8 kHz より上は拾えません。\n根本的には Bluetooth のプロファイル（HFP）の仕様の限界があるので、有線のマイクと同等の高音質にはどうやってもできない（サンプリングレートが 16 kHz なので波形は 8 kHz で頭打ちする）のは仕方がないのですが、それにしても常用は厳しいと言わざるを得ない音質でした。\nマイクの性能を重視するのであれば、Aeropex ではなく通話に特化した OpenComm の発売を待った方が良さそうです。HFP の限界はあるものの、ブームマイクが付いており、少なくとも集音性能については Aeropex よりも優れていることが期待できます（とはいえ広告記事も多く、実際に使ってみないと評価しにくいですが……）。\n個人的まとめ というわけで、音質 だけ を切り取ってしまうと、定性的にも定量的（といっても精度は高くないわけですが）にも、どうしてもネガティブな評価になってしまいます。\nが、冒頭で記述した通り、本質的には 音質よりも耳を塞がない利用形態そのもの に価値がある製品なわけで、音質を求めるのはそもそも筋違い ともいえます。\n総じて、個人的には、\n音を聴くためのコストが圧倒的に低い 耳を塞がない 装着時の圧迫感が無い 外音を聴ける 会話ができる 軽い ワイヤレスで取り回しが楽 充分な防水・防塵性能を持っている トレーニング中の利用を目論んでいるが、ひとより汗をかく体質のため、IPX5 レベルでは不十分（壊した経験あり……） Aeropex は IP67 で水没にも耐えられることが期待できる バッテリの持ちがよい 公称 8 時間と充分に長い あたりは相当ポジティブに評価していて、特に一点目がぼくにとっては非常に大きい価値です。ぼんやりと音が欲しいときに常用していくことになりそうです。\n","date":"2020-12-06T14:44:43Z","image":"/archives/3579/img/DSC06577.jpg","permalink":"/archives/3579/","title":"AfterShokz Aeropex の快適さと、音質の正直なところ"},{"content":"はじめに OpenShift 4 では、そのクラスタの構成方法に IPI（Installer-Provisioned Infrastructure）と UPI（User-Provisioned Infrastructure） の二種類があります。\nこれまで、IPI をサポートするプラットフォームは AWS や Azure、GCP、RHV、vSphere などのパブリッククラウドやオンプレミスの仮想化基盤に限定されていましたが、どうやら先月末にリリースされた OpenShift 4.6 で、ベアメタル環境への IPI インストールが公式にサポートされたようです。\nIn 4.6, the full stack automation installation of OpenShift on bare metal is generally available. Red Hat OpenShift 4.6 Is Now Available\rこれまでも、GitHub 上では ベアメタル IPI の情報は公開 されており、以前も VirtualBMC for vSphere のユースケースとしても実際に 4.5 で動作を確認済み でした。\nとはいえ、せっかく公式手順に仲間入りしたので、このエントリでは、OpenShift 4.6 で公式にサポートされたベアメタル IPI インストールの方法を、改めて紹介します。ただし、BMC を積んだ物理サーバが潤沢にあるわけではないので、物理サーバは VirtualBMC for vSphere を使った vSphere 環境の仮想マシンで代替します。\nなお、今回紹介する手順は OKD でなく OpenShift のものです。インストールすると Red Hat OpenShift Cluster Manager に登録され、60 日間の評価期間 が始まります。\n手順の概要 公式の手順を参考に進めます。IPI インストールといえどクラスタ外に本当に何も要らないわけではなく、外部との接続点になるルータ、DHCP サーバ、DNS サーバは必要です。また、物理サーバを自動制御するため、各物理サーバの BMC へも到達できる必要があります。\nOverview - Deploying installer-provisioned clusters on bare metal | Installing | OpenShift Container Platform 4.6 インストールの流れ インストールの中では、最初にブートストラップ VM が構成されて起動されます。この後、コントロールプレーンノードの BMC が制御され、ブートストラップ VM を使って PXE ブートと OS の導入、初期構成が行われます。この段階で一時的にブートストラップ VM が Kubernetes のコントロールプレーンとして振舞うようになり、これを使ってコントロールプレーンノード上にマネジメントクラスタが構成されます。\nOverview - Deploying installer-provisioned clusters on bare metal | Installing | OpenShift Container Platform 4.6\rこの後、ブートストラップ VM からコントロールプレーンの機能がマネジメントクラスタに移譲され、ブートストラップ VM は削除されます。後続のワーカノードのプロビジョニングはマネジメントクラスタを使って行われます。\nOverview - Deploying installer-provisioned clusters on bare metal | Installing | OpenShift Container Platform 4.6\rブートストラップクラスタを用いて Kubernetes のクラスタそれ自体をプロビジョニングしていく流れは、Kubernetes の Cluster API のそれとよく似ていますね。\n今回の構成 さて、公式には前述の通りの構成ですが、今回はラボ向けに簡略化して次の図の通りに構成します。といっても、きちんとマルチノードには構成します。\nポイントは次の三点です。\nクラスタ外に必要なルータ、DHCP サーバ、DNS サーバの機能は、仮想マシン（図中のユーティリティ VM）にサービスさせる プロビジョニングノード、マスタノード、ワーカノードはすべて vSphere 環境の仮想マシンとして用意する vSphere 環境の仮想マシンを物理サーバとして振舞わせるため、ユーティリティ VM に VirtualBMC for vSphere を導入し、仮想マシンごとに仮想 BMC を構成する 準備 インストールの前提として、物理サーバ（に相当する仮想マシン）や周辺環境を用意します。\n物理サーバに相当する仮想マシンの作成 構成先となる物理サーバに相当するモノとして、vSphere 環境上に仮想マシンを用意します。\n役割 vCPU メモリ ディスク NIC #1 NIC #2 マスタノード #1 4 16 GB 128 GB Provisioning Baremetal マスタノード #2 4 16 GB 128 GB Provisioning Baremetal マスタノード #3 4 16 GB 128 GB Provisioning Baremetal ワーカノード #1 4 16 GB 128 GB Provisioning Baremetal ワーカノード #2 4 16 GB 128 GB Provisioning Baremetal プロビジョニング 4 16 GB 128 GB Provisioning Baremetal 上記の仮想マシンは、ファームウェアに BIOS を指定しておきます。これは VirtualBMC for vSphere の都合なので、ホントに物理サーバで実施するときにはこの必要はありません。ドキュメントからはプロビジョニングネットワークに IPv6 を使う場合は UEFI でないとダメなように読み取れますが、今回は IPv4 を利用するのでヨシとします。\nまた、プロビジョニング用 VM では、後続の手順で libvirt を動作させるため、ハードウェア仮想化をゲストに公開する構成にしておきます。\nなお、プロビジョニング用 VM を除き、各ノードの OS は IPI インストールの手順の中で自動でインストールされるため、手動での導入は不要です。プロビジョニング用 VM の OS インストールは後続の手順で行います。\n仮想マシンができたら、全台で各 NIC（二つ）の MAC アドレスを確認しておきます。後続の手順で追って必要になります。確認には PowerCLI が便利です。\n\u0026gt; get-vm ocp-* | get-networkadapter | select parent, networkname, macaddress | where {$_.networkname -eq \u0026#34;vlan11-int-ocp-baremetal\u0026#34;} | sort parent Parent NetworkName MacAddress ------ ----------- ---------- ocp-master0 vlan11-int-ocp-baremetal 00:50:56:86:b1:1a ocp-master1 vlan11-int-ocp-baremetal 00:50:56:86:ad:2a ocp-master2 vlan11-int-ocp-baremetal 00:50:56:86:76:93 ocp-provisioner vlan11-int-ocp-baremetal 00:50:56:86:25:d6 ocp-worker0 vlan11-int-ocp-baremetal 00:50:56:86:12:86 ocp-worker1 vlan11-int-ocp-baremetal 00:50:56:86:22:0b \u0026gt; get-vm ocp-* | get-networkadapter | select parent, networkname, macaddress | where {$_.networkname -eq \u0026#34;vlan10-int-ocp-provision\u0026#34;} | sort parent Parent NetworkName MacAddress ------ ----------- ---------- ocp-master0 vlan10-int-ocp-provision 00:50:56:86:32:ec ocp-master1 vlan10-int-ocp-provision 00:50:56:86:52:17 ocp-master2 vlan10-int-ocp-provision 00:50:56:86:dc:65 ocp-provisioner vlan10-int-ocp-provision 00:50:56:86:c6:e0 ocp-worker0 vlan10-int-ocp-provision 00:50:56:86:10:5b ocp-worker1 vlan10-int-ocp-provision 00:50:56:86:d0:a0 ユーティリティ VM の構成 図中のベアメタルネットワーク上にルータや DHCP サーバ、DNS サーバが必要なので、これを用意します。が、物理的なモノは用意できないので、今回は仮想マシン上で構成します。大した機能は動かさないので、スペックは最小限で大丈夫です。\n役割 vCPU メモリ ディスク NIC #1 NIC #2 ユーティリティ 1 2 GB 16 GB Home Baremetal OS は最新の RHEL 8.3 を導入しました。最小限の構成でインストールした後、ルータや DHCP サーバ、DNS サーバを作成していきます。\n初期設定 IP アドレスとホスト名などを構成します。\nlocalectl set-keymap jp106 hostnamectl set-hostname util.ocp.kuro.lab nmcli con mod ens192 connection.autoconnect yes ipv4.method manual ipv4.addresses 192.168.0.240/24 ipv4.gateway 192.168.0.1 ipv4.dns 192.168.0.1 nmcli con mod ens224 connection.autoconnect yes ipv4.method manual ipv4.addresses 10.0.0.254/24 reboot ルータとしての設定 ルータといいつつ単に IP マスカレードを使うようにするだけです。今回は、ベアメタルネットワークとホームネットワークの間で双方向に NAPT させます。firewalld で事前定義されているゾーンのうち、external は masquarade がデフォルトで yes なので、手動での変更は、ゾーンの切り替えと internal の IP マスカレードの有効化だけです。\nnmcli con mod ens192 connection.zone external nmcli con mod ens224 connection.zone internal firewall-cmd --add-masquerade --zone=internal --permanent firewall-cmd --reload DNS サーバの構成 ドキュメントの Network requirements にはあまり情報が書いていませんが、クラスタで利用するノードや仮想 IP アドレス類を外部から名前解決できるようにします。\nここでは、dnsmasq を利用します。基本、/etc/hosts と同じ書式のファイルを読ませるだけですが、それではワイルドカードの名前解決は表現できないので、*.apps 相当のエントリのみ設定ファイルに address 行でベタ書きしています。\ndnf install -y dnsmasq systemctl enable dnsmasq systemctl start dnsmasq cat \u0026lt;\u0026lt; EOF \u0026gt; /etc/hosts.dnsmasq 10.0.0.10 openshift-master-0 10.0.0.11 openshift-master-1 10.0.0.12 openshift-master-2 10.0.0.20 openshift-worker-0 10.0.0.21 openshift-worker-1 10.0.0.50 api 10.0.0.253 provisioner EOF cat \u0026lt;\u0026lt; EOF \u0026gt; /etc/dnsmasq.d/dns.dns domain-needed bind-dynamic bogus-priv domain=ocp.kuro.lab interface=ens224 no-hosts addn-hosts=/etc/hosts.dnsmasq expand-hosts address=/.apps.ocp.kuro.lab/10.0.0.52 EOF systemctl restart dnsmasq firewall-cmd --add-service=dns --zone=internal --permanent firewall-cmd --reload DHCP サーバの構成 これもドキュメントの Network requirements にはあまり細かく書いていませんが、静的 DHCP エントリを作成して、DHCP クライアントの MAC アドレスに応じて IP アドレスとホスト名が自動で応答されるようにします。\nこれにも、dnsmasq を利用します。\ncat \u0026lt;\u0026lt; EOF \u0026gt; /etc/dnsmasq.d/dhcp.dns domain-needed bind-dynamic bogus-priv domain=ocp.kuro.lab dhcp-range=10.0.0.10,10.0.0.49 dhcp-option=3,10.0.0.254 resolv-file=/etc/resolv.conf.upstream interface=ens224 server=10.0.0.254 dhcp-host=00:50:56:86:25:d6,provisioner.ocp.kuro.lab,10.0.0.253 dhcp-host=00:50:56:86:b1:1a,openshift-master-0.ocp.kuro.lab,10.0.0.10 dhcp-host=00:50:56:86:ad:2a,openshift-master-1.ocp.kuro.lab,10.0.0.11 dhcp-host=00:50:56:86:76:93,openshift-master-2.ocp.kuro.lab,10.0.0.12 dhcp-host=00:50:56:86:12:86,openshift-worker-0.ocp.kuro.lab,10.0.0.20 dhcp-host=00:50:56:86:22:0b,openshift-worker-1.ocp.kuro.lab,10.0.0.21 EOF cat \u0026lt;\u0026lt; EOF \u0026gt; /etc/resolv.conf.upstream nameserver 192.168.0.1 EOF systemctl restart dnsmasq firewall-cmd --add-service=dhcp --zone=internal --permanent firewall-cmd --reload 記述する MAC アドレスは、各ノードのベアメタルネットワーク側の NIC のものです。\nVirtualBMC for vSphere の構成 インストール先のノードは本来は物理サーバで用意しますが、今回は vSphere 環境の仮想マシンで代替します。しかしながら、IPI インストールでは物理サーバの BMC を使う要件があり、単に仮想マシンを立てただけでは不十分です。\nそこで、vSphere 環境の仮想マシンに仮想 BMC インタフェイスを提供できる VirtualBMC for vSphere を使って、仮想マシンを IPMI で制御できるようにします。\ndnf install -y python38 python3 -m pip install -U pip python3 -m pip install vbmc4vsphere vbmcd vbmc add ocp-master0 --port 6230 --viserver kuro-vcs01.krkb.lab --viserver-username vbmc@vsphere.local --viserver-password ******** vbmc add ocp-master1 --port 6231 --viserver kuro-vcs01.krkb.lab --viserver-username vbmc@vsphere.local --viserver-password ******** vbmc add ocp-master2 --port 6232 --viserver kuro-vcs01.krkb.lab --viserver-username vbmc@vsphere.local --viserver-password ******** vbmc add ocp-worker0 --port 6233 --viserver kuro-vcs01.krkb.lab --viserver-username vbmc@vsphere.local --viserver-password ******** vbmc add ocp-worker1 --port 6234 --viserver kuro-vcs01.krkb.lab --viserver-username vbmc@vsphere.local --viserver-password ******** vbmc start ocp-master0 vbmc start ocp-master1 vbmc start ocp-master2 vbmc start ocp-worker0 vbmc start ocp-worker1 firewall-cmd --add-port=6230-6234/udp --zone=internal --permanent firewall-cmd --reload プロビジョニング VM の構成 前の手順で作成したプロビジョニング VM に、RHEL 8.3 を最小限の構成で導入して構成していきます。\nOS インストールの手順自体は割愛しますが、前述の DHCP サーバや DNS サーバ、ルータの機能が正しく機能していれば、何もしなくても MAC アドレスを基に静的な IP アドレスとホスト名が正しく取得されるほか、インタネットに名前解決を含めて出られるようになっているはずです。また、ホームネットワーク内の端末で 10.0.0.0/24 宛のルートをユーティリティ VM に向けて構成すれば、そこから直接プロビジョニング VM に SSH できるようになるはずです。\nOS のインストールができたら、ここからは愚直にドキュメントに従っていきます。\nSetting up the environment for an OpenShift installation - Deploying installer-provisioned clusters on bare metal | Installing | OpenShift Container Platform 4.6 設定ファイルの作成まで ところどころ変なところがあるので、注記が必要なところだけピックアップします。\nPreparing the provisioner node for OpenShift Container Platform installation 途中、sudo nohup bash -c で nmcli を流し込む手順がありますが、クォートがシングルなので環境変数が展開されずに期待通りに構成されません（#27579）。また、IPv4 の場合は provisioning には IPv4 の IP アドレスを振ります。よって、次のようにします。\n[kni@provisioner ~]$ export PUB_CONN=\u0026lt;baremetal_nic_name\u0026gt; [kni@provisioner ~]$ export PROV_CONN=\u0026lt;prov_nic_name\u0026gt; [kni@provisioner ~]$ sudo -E nohup bash -c \u0026#39; nmcli con down \u0026#34;$PROV_CONN\u0026#34; nmcli con down \u0026#34;$PUB_CONN\u0026#34; nmcli con delete \u0026#34;$PROV_CONN\u0026#34; nmcli con delete \u0026#34;$PUB_CONN\u0026#34; # RHEL 8.1 appends the word \u0026#34;System\u0026#34; in front of the connection, delete in case it exists nmcli con down \u0026#34;System $PUB_CONN\u0026#34; nmcli con delete \u0026#34;System $PUB_CONN\u0026#34; nmcli connection add ifname provisioning type bridge con-name provisioning nmcli con add type bridge-slave ifname \u0026#34;$PROV_CONN\u0026#34; master provisioning nmcli connection add ifname baremetal type bridge con-name baremetal nmcli con add type bridge-slave ifname \u0026#34;$PUB_CONN\u0026#34; master baremetal pkill dhclient; dhclient baremetal nmcli connection modify provisioning ipv4.addresses 172.22.0.1/24 ipv4.method manual nmcli con down provisioning nmcli con up provisioning \u0026#39; Extracting the OpenShift Container Platform installer curl で oc のバイナリをダウンロードしますが、引数で与えている URL が 404 になって失敗します（#27495）。ファイル名に -$VERSION は不要なので、次のようにします。\n[kni@provisioner ~]$ curl -s https://mirror.openshift.com/pub/openshift-v4/clients/ocp/$VERSION/openshift-client-linux.tar.gz | tar zxvf - oc Creating an RHCOS images cache (optional) firewall-cmd で 8080/tcp を追加しますが、その後リロードしていないので実際には通信できないままになってしまいます（#27585）。リロードします。\n[kni@provisioner ~]$ sudo firewall-cmd --reload インストーラから Commit ID を取得する手順では、バイナリの位置が違っているので失敗します（#27583）。カレントディレクトリのバイナリを使ってもよいですが、どちらかというとバイナリの /usr/local/bin へのコピーが抜けている感触なので、そのようにします。\n[kni@provisioner ~]$ sudo cp ./openshift-baremetal-install /usr/local/bin [kni@provisioner ~]$ export COMMIT_ID=$(/usr/local/bin/openshift-baremetal-install version | grep \u0026#39;^built from commit\u0026#39; | awk \u0026#39;{print $4}\u0026#39;) curl でイメージをダウンロードする手順では、-o でディレクトリ名を与えているので失敗します（#27584）。-o にファイル名を加えて回避します。\n[kni@provisioner ~]$ curl -L ${RHCOS_PATH}${RHCOS_QEMU_URI} -o /home/kni/rhcos_image_cache/${RHCOS_QEMU_URI} [kni@provisioner ~]$ curl -L ${RHCOS_PATH}${RHCOS_OPENSTACK_URI} -o /home/kni/rhcos_image_cache/${RHCOS_OPENSTACK_URI} 設定ファイルの作成 いちばん大事なファイルです。サンプルを参考に install-config.yaml を作成します。\napiVersion: v1 baseDomain: kuro.lab metadata: name: ocp networking: machineCIDR: 10.0.0.0/24 networkType: OVNKubernetes compute: - name: worker replicas: 2 controlPlane: name: master replicas: 3 platform: baremetal: {} platform: baremetal: apiVIP: 10.0.0.50 ingressVIP: 10.0.0.52 provisioningBridge: provisioning provisioningNetworkInterface: ens192 provisioningNetworkCIDR: 172.22.0.0/24 hosts: - name: openshift-master-0 role: master bmc: address: ipmi://10.0.0.254:6230 username: admin password: password bootMACAddress: 00:50:56:86:32:ec - name: openshift-master-1 role: master bmc: address: ipmi://10.0.0.254:6231 username: admin password: password bootMACAddress: 00:50:56:86:52:17 - name: openshift-master-2 role: master bmc: address: ipmi://10.0.0.254:6232 username: admin password: password bootMACAddress: 00:50:56:86:dc:65 - name: openshift-worker-0 role: worker bmc: address: ipmi://10.0.0.254:6233 username: admin password: password bootMACAddress: 00:50:56:86:10:5b - name: openshift-worker-1 role: worker bmc: address: ipmi://10.0.0.254:6234 username: admin password: password bootMACAddress: 00:50:56:86:d0:a0 bootstrapOSImage: http://provisioner.ocp.kuro.lab:8080/rhcos-46.82.202010011740-0-qemu.x86_64.qcow2.gz?sha256=cbaf2e5548af2d1d1153d9a1beca118042e770725166de427792c33a875137cc clusterOSImage: http://provisioner.ocp.kuro.lab:8080/rhcos-46.82.202010011740-0-openstack.x86_64.qcow2.gz?sha256=9d88edcacbdf31db9cb9418ee3721c74a3f5bbfc4904db9e383e674a86335bd1 pullSecret: \u0026#39;{\u0026#34;auths\u0026#34;:{...}}\u0026#39; sshKey: \u0026#39;ssh-rsa ... kni@provisioner\u0026#39; ポイントは以下です。\nplatform.baremetal.provisioningNetworkInterface ドキュメントに記載はありませんが、無いと次のメッセージで怒られました FATAL failed to fetch Cluster: failed to fetch dependency of \u0026quot;Cluster\u0026quot;: failed to generate asset \u0026quot;Platform Provisioning Check\u0026quot;: platform.baremetal.provisioningNetworkInterface: Invalid value: \u0026quot;\u0026quot;: no provisioning network interface is configured, please set this value to be the interface on the provisioning network on your cluster's baremetal hosts platform.baremetal.hosts[*].bmc 物理サーバのごとの BMC の IP アドレスと認証情報です 今回の例では、行き先は VirtualBMC です。ノードごとに対応する IP アドレスとポート番号と認証情報を入れています platform.baremetal.hosts[*].bootMACAddress ノードごとのプロビジョニングネットワーク側の NIC の MAC アドレスです ノードが PXE ブートする際、ブートストラップ VM 内で動作する DHCP サーバが対象のノードを判断するときに使われます platform.baremetal.bootstrapOSImage、platform.baremetal.clusterOSImage 今回はオプショナルの手順を実行して、 RHCOS のイメージキャッシュをプロビジョニング VM 上に取得し、HTTP で公開しています。インストーラの中でそのキャッシュを利用するため、URL を指定しています URL にはファイル名とハッシュ値を含める必要がありますが、必要な情報は前段の手順で環境変数に保存済みで、それぞれ次のコマンドで得られます echo ${RHCOS_QEMU_URI}?sha256=${RHCOS_QEMU_SHA_UNCOMPRESSED} echo ${RHCOS_OPENSTACK_URI}?sha256=${RHCOS_OPENSTACK_SHA_COMPRESSED} pullSecret 前段の手順で作成した pull-secret.txt の中身をそのまま記入します sshKey 前段の手順で作成した /home/kni/.ssh/id_rsa.pub の中身をそのまま記入します。 ファイルができたら、インストール用のマニフェストを作成します。\nmkdir ~/clusterconfigs cp install-config.yaml ~/clusterconfigs ./openshift-baremetal-install --dir ~/clusterconfigs create manifests インストール ここまでできたら、インストーラを走らせて待つだけです。\n実行 ./openshift-baremetal-install --dir ~/clusterconfigs --log-level debug create cluster 進捗は画面にも出ますが、ログファイルとしても出力されます。\ntail -f ~/clusterconfigs/.openshift_install.log 観察 何も起きなければ放置するだけですが、興味があれば、構成の途中では、プロビジョニング VM 上で動作するブートストラップ VM の様子をみたり、\n$ sudo virsh list Id Name State ------------------------------------- 1 ocp-6vw5v-bootstrap running ブートストラップ VM のコンソール出力を確認したり、\n$ sudo virsh console ocp-6vw5v-bootstrap Connected to domain ocp-6vw5v-bootstrap Escape character is ^] [*** ] A start job is running for Ignition (fetch-offline) (47s / no limit) ... その IP アドレスを確認して SSH したり、\n$ sudo virsh console ocp-6vw5v-bootstrap Connected to domain ocp-6vw5v-bootstrap Escape character is ^] Red Hat Enterprise Linux CoreOS 46.82.202010011740-0 (Ootpa) 4.6 ... ens3: 10.0.0.30 fe80::5054:ff:fe42:654d ens4: 172.22.0.2 fe80::5607:c92e:39f2:add1 localhost login: $ ssh core@172.22.0.2 ... [core@localhost ~]$ さらにその中で Podman の様子を見たり、\n[core@localhost ~]$ sudo podman ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ... 7d77a65def19 quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:796617722a915d79b478c9623b1d152a397478a7f6ba7ec71d39b9df2668cc80 35 seconds ago Up 32 seconds ago httpd 051f0c4e27d2 quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:796617722a915d79b478c9623b1d152a397478a7f6ba7ec71d39b9df2668cc80 35 seconds ago Up 35 seconds ago mariadb ebed2f88723f quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:796617722a915d79b478c9623b1d152a397478a7f6ba7ec71d39b9df2668cc80 35 seconds ago Up 35 seconds ago dnsmasq [core@localhost ~]$ sudo podman logs -f ironic-conductor ... 他にも、仮想マシンが PXE ブートしたり、勝手にブートデバイスが切り替わりながら再起動していく様子を眺めたりするとおもしろいですね。手を一切触れずにごりごりインストールが進むのは気持ちがよいものです。\nログ抜粋 最終的には、ログはこんな感じになります。抜粋です。\n[kni@provisioner ~]$ ./openshift-baremetal-install --dir ~/clusterconfigs --log-level debug create cluster DEBUG OpenShift Installer 4.6.4 DEBUG Built from commit 6e02d049701437fa81521fe981405745a62c86c5 DEBUG Fetching Metadata... DEBUG Loading Metadata... ... DEBUG Generating Metadata... DEBUG Fetching Terraform Variables... DEBUG Loading Terraform Variables... ... DEBUG Generating Terraform Variables... INFO Obtaining RHCOS image file from \u0026#39;http://provisioner.ocp.kuro.lab:8080/rhcos-46.82.202010011740-0-qemu.x86_64.qcow2.gz?sha256=cbaf2e5548af2d1d1153d9a1beca118042e770725166de427792c33a875137cc\u0026#39; DEBUG Unpacking file into \u0026#34;/home/kni/.cache/openshift-installer/image_cache/da2e0c98a60ddd80b338f1c07e95470c\u0026#34;... DEBUG decompressing the image archive as gz DEBUG Checksum validation is complete... ... DEBUG Generating Cluster... INFO Creating infrastructure resources... ... DEBUG Initializing modules... DEBUG - bootstrap in ../../tmp/openshift-install-671464379/bootstrap DEBUG - masters in ../../tmp/openshift-install-671464379/masters DEBUG DEBUG Initializing the backend... DEBUG DEBUG Initializing provider plugins... DEBUG DEBUG Terraform has been successfully initialized! DEBUG DEBUG You may now begin working with Terraform. Try running \u0026#34;terraform plan\u0026#34; to see DEBUG any changes that are required for your infrastructure. All Terraform commands DEBUG should now work. DEBUG DEBUG If you ever set or change modules or backend configuration for Terraform, DEBUG rerun this command to reinitialize your working directory. If you forget, other DEBUG commands will detect it and remind you to do so if necessary. DEBUG module.masters.ironic_node_v1.openshift-master-host[1]: Creating... DEBUG module.masters.ironic_node_v1.openshift-master-host[0]: Creating... DEBUG module.masters.ironic_node_v1.openshift-master-host[2]: Creating... DEBUG module.bootstrap.libvirt_pool.bootstrap: Creating... DEBUG module.bootstrap.libvirt_ignition.bootstrap: Creating... ... DEBUG module.bootstrap.libvirt_pool.bootstrap: Creation complete after 2m0s [id=4c9066e8-7240-4618-ac54-d7fc408b3577] DEBUG module.bootstrap.libvirt_volume.bootstrap: Creating... DEBUG module.bootstrap.libvirt_ignition.bootstrap: Still creating... [2m0s elapsed] DEBUG module.bootstrap.libvirt_ignition.bootstrap: Creation complete after 2m0s [id=/var/lib/libvirt/images/ocp-696dd-bootstrap.ign;5fbbb954-a9a5-e294-8706-526fed148028] DEBUG module.bootstrap.libvirt_volume.bootstrap: Creation complete after 3s [id=/var/lib/libvirt/openshift-images/ocp-696dd-bootstrap/ocp-696dd-bootstrap] DEBUG module.bootstrap.libvirt_domain.bootstrap: Creating... DEBUG module.bootstrap.libvirt_domain.bootstrap: Creation complete after 1s [id=974fd0fc-f96a-4a32-bf2a-15b19f6b1903] ... DEBUG module.masters.ironic_node_v1.openshift-master-host[2]: Still creating... [16m0s elapsed] DEBUG module.masters.ironic_node_v1.openshift-master-host[0]: Still creating... [16m0s elapsed] DEBUG module.masters.ironic_node_v1.openshift-master-host[1]: Still creating... [16m0s elapsed] DEBUG module.masters.ironic_node_v1.openshift-master-host[0]: Creation complete after 16m8s [id=e3fc6aa6-08c2-452e-bd70-de55a7655d2a] DEBUG module.masters.ironic_node_v1.openshift-master-host[1]: Creation complete after 16m8s [id=1e8c4e48-f0de-4910-a9da-3482590f38bf] DEBUG module.masters.ironic_node_v1.openshift-master-host[2]: Creation complete after 16m8s [id=21687363-7580-4473-abb2-16cb5022d672] DEBUG module.masters.data.ironic_introspection.openshift-master-introspection[2]: Refreshing state... DEBUG module.masters.data.ironic_introspection.openshift-master-introspection[1]: Refreshing state... DEBUG module.masters.data.ironic_introspection.openshift-master-introspection[0]: Refreshing state... DEBUG module.masters.ironic_allocation_v1.openshift-master-allocation[2]: Creating... DEBUG module.masters.ironic_allocation_v1.openshift-master-allocation[0]: Creating... DEBUG module.masters.ironic_allocation_v1.openshift-master-allocation[1]: Creating... DEBUG module.masters.ironic_allocation_v1.openshift-master-allocation[1]: Creation complete after 2s [id=856f28b9-ae7f-4584-a34b-2be2050f5ebd] DEBUG module.masters.ironic_allocation_v1.openshift-master-allocation[2]: Creation complete after 2s [id=f4a48b25-7046-4588-8c97-9838be83d66c] DEBUG module.masters.ironic_allocation_v1.openshift-master-allocation[0]: Creation complete after 2s [id=c4bc140a-6bbe-47c1-afcb-88c08b6d688f] DEBUG module.masters.ironic_deployment.openshift-master-deployment[2]: Creating... DEBUG module.masters.ironic_deployment.openshift-master-deployment[1]: Creating... DEBUG module.masters.ironic_deployment.openshift-master-deployment[0]: Creating... ... DEBUG module.masters.ironic_deployment.openshift-master-deployment[0]: Still creating... [7m0s elapsed] DEBUG module.masters.ironic_deployment.openshift-master-deployment[1]: Still creating... [7m0s elapsed] DEBUG module.masters.ironic_deployment.openshift-master-deployment[2]: Still creating... [7m0s elapsed] DEBUG module.masters.ironic_deployment.openshift-master-deployment[1]: Creation complete after 7m1s [id=1e8c4e48-f0de-4910-a9da-3482590f38bf] DEBUG module.masters.ironic_deployment.openshift-master-deployment[0]: Creation complete after 7m1s [id=21687363-7580-4473-abb2-16cb5022d672] DEBUG module.masters.ironic_deployment.openshift-master-deployment[2]: Creation complete after 7m1s [id=e3fc6aa6-08c2-452e-bd70-de55a7655d2a] DEBUG DEBUG Apply complete! Resources: 13 added, 0 changed, 0 destroyed. DEBUG OpenShift Installer 4.6.4 DEBUG Built from commit 6e02d049701437fa81521fe981405745a62c86c5 INFO Waiting up to 20m0s for the Kubernetes API at https://api.ocp.kuro.lab:6443... INFO API v1.19.0+9f84db3 up INFO Waiting up to 30m0s for bootstrapping to complete... DEBUG Bootstrap status: complete INFO Destroying the bootstrap resources... ... DEBUG Initializing modules... DEBUG - bootstrap in ../../tmp/openshift-install-804823006/bootstrap DEBUG - masters in ../../tmp/openshift-install-804823006/masters DEBUG DEBUG Initializing the backend... DEBUG DEBUG Initializing provider plugins... DEBUG DEBUG Terraform has been successfully initialized! DEBUG DEBUG You may now begin working with Terraform. Try running \u0026#34;terraform plan\u0026#34; to see DEBUG any changes that are required for your infrastructure. All Terraform commands DEBUG should now work. DEBUG DEBUG If you ever set or change modules or backend configuration for Terraform, DEBUG rerun this command to reinitialize your working directory. If you forget, other DEBUG commands will detect it and remind you to do so if necessary. DEBUG module.bootstrap.libvirt_pool.bootstrap: Refreshing state... [id=4c9066e8-7240-4618-ac54-d7fc408b3577] DEBUG module.bootstrap.libvirt_volume.bootstrap: Refreshing state... [id=/var/lib/libvirt/openshift-images/ocp-696dd-bootstrap/ocp-696dd-bootstrap] DEBUG module.bootstrap.libvirt_ignition.bootstrap: Refreshing state... [id=/var/lib/libvirt/images/ocp-696dd-bootstrap.ign;5fbbb954-a9a5-e294-8706-526fed148028] DEBUG module.bootstrap.libvirt_domain.bootstrap: Refreshing state... [id=974fd0fc-f96a-4a32-bf2a-15b19f6b1903] DEBUG module.bootstrap.libvirt_domain.bootstrap: Destroying... [id=974fd0fc-f96a-4a32-bf2a-15b19f6b1903] DEBUG module.bootstrap.libvirt_domain.bootstrap: Destruction complete after 1s DEBUG module.bootstrap.libvirt_volume.bootstrap: Destroying... [id=/var/lib/libvirt/openshift-images/ocp-696dd-bootstrap/ocp-696dd-bootstrap] DEBUG module.bootstrap.libvirt_ignition.bootstrap: Destroying... [id=/var/lib/libvirt/images/ocp-696dd-bootstrap.ign;5fbbb954-a9a5-e294-8706-526fed148028] DEBUG module.bootstrap.libvirt_volume.bootstrap: Destruction complete after 0s DEBUG module.bootstrap.libvirt_ignition.bootstrap: Destruction complete after 0s DEBUG module.bootstrap.libvirt_pool.bootstrap: Destroying... [id=4c9066e8-7240-4618-ac54-d7fc408b3577] DEBUG module.bootstrap.libvirt_pool.bootstrap: Destruction complete after 5s ... DEBUG Destroy complete! Resources: 4 destroyed. DEBUG Loading Install Config... DEBUG Loading SSH Key... DEBUG Loading Base Domain... DEBUG Loading Platform... DEBUG Loading Cluster Name... DEBUG Loading Base Domain... DEBUG Loading Platform... DEBUG Loading Pull Secret... DEBUG Loading Platform... DEBUG Using Install Config loaded from state file INFO Waiting up to 1h0m0s for the cluster at https://api.ocp.kuro.lab:6443 to initialize... DEBUG Still waiting for the cluster to initialize: Working towards 4.6.4: 98% complete, waiting on authentication, cluster-autoscaler, console, ingress, kube-storage-version-migrator, machine-api, monitoring DEBUG Still waiting for the cluster to initialize: Working towards 4.6.4: 99% complete DEBUG Still waiting for the cluster to initialize: Working towards 4.6.4: 99% complete DEBUG Still waiting for the cluster to initialize: Working towards 4.6.4: 99% complete, waiting on authentication, console, ingress, kube-storage-version-migrator, monitoring DEBUG Still waiting for the cluster to initialize: Some cluster operators are still updating: authentication, console, ingress, kube-storage-version-migrator, monitoring DEBUG Still waiting for the cluster to initialize: Some cluster operators are still updating: authentication, console, monitoring DEBUG Still waiting for the cluster to initialize: Working towards 4.6.4: 99% complete DEBUG Cluster is initialized INFO Waiting up to 10m0s for the openshift-console route to be created... DEBUG Route found in openshift-console namespace: console DEBUG Route found in openshift-console namespace: downloads DEBUG OpenShift console route is created INFO Install complete! INFO To access the cluster as the system:admin user when using \u0026#39;oc\u0026#39;, run \u0026#39;export KUBECONFIG=/home/kni/clusterconfigs/auth/kubeconfig\u0026#39; INFO Access the OpenShift web-console here: https://console-openshift-console.apps.ocp.kuro.lab INFO Login to the console with user: \u0026#34;kubeadmin\u0026#34;, and password: \u0026#34;*****-*****-*****-*****\u0026#34; DEBUG Time elapsed per stage: DEBUG Infrastructure: 23m17s DEBUG Bootstrap Complete: 26m22s DEBUG Bootstrap Destroy: 11s DEBUG Cluster Operators: 32m45s INFO Time elapsed: 1h22m58s 完成 手元の環境では、だいたい一時間半弱くらいで完成しました。CPU がサチっていたので、つよい環境ならもっと早いでしょう。\n完成した OpenShift 環境には、インストーラの画面かログの末尾に出力されている URL と認証情報（kubeadmin とそのパスワード）を使ってログインできます。パスワードは ~/clusterconfigs/auth/kubeadmin-password にも平文で保存されています。\n管理画面でも、Bare Metal Hosts として登録されている様子が確認できます。\nトラブルシュート なにかが起きて失敗したとき、失敗の原因は本当にさまざまですが、ドキュメントのトラブルシュートのページには良い情報がたくさん載っているので、困ったときには参考にできます。\nTroubleshooting - Deploying installer-provisioned clusters on bare metal | Installing | OpenShift Container Platform 4.6 インストールを再実行したいときは、次のように掃除して clusterconfigs ディレクトリの作成からやりなおすと確実です。トラブルシュートのページに記載のものだと libvirt の掃除は不完全（#27618）です。\nfor i in $(sudo virsh list | tail -n +3 | grep bootstrap | awk {\u0026#39;print $2\u0026#39;}); do sudo virsh destroy $i; sudo virsh undefine $i; sudo virsh vol-delete $i --pool $i; sudo virsh vol-delete $i.ign --pool default; sudo virsh pool-destroy $i; sudo virsh pool-undefine $i; done rm -rf ~/clusterconfigs rm -rf ~/.cache 再実行前には、インストール先のノードはすべて停止させます。\nなお、プロビジョニング VM や各ノードの負荷が高いと、ところどころインストーラの出力に Failed や failure などネガティブな表現が混ざることがあるようですが、基本的には ERROR でインストーラ自体が止まらない限りは自動でリトライされて進むはずなので、まずは待つのがよさそうです。\n簡易初期設定と動作確認 クラスタができても使えなかったら意味がないので、イメージレジストリを構成して、ユーザを追加し、サンプルアプリケーションのビルドと実行ができることくらいは確認しておきます。\nイメージレジストリの追加 ベアメタルで構成した場合、組み込みのイメージレジストリはインストール直後は無効化されているので、まずはこれを有効化します。有効化しないと新規イメージのビルドができません。\n本来は PV をきちんとつくって永続化すべきですが、いったん emptyDir で逃げます。oc で操作しますが、認証情報はクラスタ作成後に /home/kni/clusterconfigs/auth/kubeconfig に保存されるのでそれを利用します（system:admin になれます）。\n[kni@provisioner ~]$ export KUBECONFIG=/home/kni/clusterconfigs/auth/kubeconfig [kni@provisioner ~]$ oc patch configs.imageregistry.operator.openshift.io cluster --type merge --patch \u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;managementState\u0026#34;:\u0026#34;Managed\u0026#34;}}\u0026#39; [kni@provisioner ~]$ oc patch configs.imageregistry.operator.openshift.io cluster --type merge --patch \u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;storage\u0026#34;:{\u0026#34;emptyDir\u0026#34;:{}}}}\u0026#39; この操作で、spec.managementState が Managed になって、spec.storage が emptyDir で構成されます。\n[kni@provisioner ~]$ oc get configs.imageregistry.operator.openshift.io cluster -o jsonpath={.spec} | jq { ... \u0026#34;managementState\u0026#34;: \u0026#34;Managed\u0026#34;, ... \u0026#34;storage\u0026#34;: { \u0026#34;emptyDir\u0026#34;: {}, \u0026#34;managementState\u0026#34;: \u0026#34;Managed\u0026#34; }, ... } ユーザの追加 インストーラによって作成されたユーザ kubeadmin は、スーパユーザすぎて常用すべきではないので、別のユーザを作ります。\nユーザを追加するにはまずは IDP を登録しなければならないので、ここではいちばん手軽な HTPasswd を構成します。\nまずは HTPasswd ファイルを作成します。\n[kni@provisioner ~]$ sudo dnf install -y httpd-tools [kni@provisioner ~]$ htpasswd -c -B -b ~/clusterconfigs/auth/htpasswd lab-admin VMware123! 続いて、作成した HTPasswd ファイルを中身にしたシークレットを作成します。GUI からも操作できますが、せっかくなので oc します。\n[kni@provisioner ~]$ oc create secret generic htpasswd-secret --from-file=htpasswd=$(pwd)/clusterconfigs/auth/htpasswd -n openshift-config IDP を定義するカスタムリソースのマニフェストを作って流し込みます。これも同等のことが GUI でも可能です。\n[kni@provisioner ~]$ cat \u0026lt;\u0026lt; EOF \u0026gt; ~/clusterconfigs/auth/idp.yaml apiVersion: config.openshift.io/v1 kind: OAuth metadata: name: cluster spec: identityProviders: - name: htpasswd-provider mappingMethod: claim type: HTPasswd htpasswd: fileData: name: htpasswd-secret EOF [kni@provisioner ~]$ oc apply -f ~/clusterconfigs/auth/idp.yaml これで認証できるようになったので、テスト用に新しいプロジェクトを作って admin ロールを割り当てます。この段階では lab-admin ユーザは一度もログインしていないため内部的に存在しないので警告が出ますが、問題ありません。\n[kni@provisioner ~]$ oc new-project lab-sample [kni@provisioner ~]$ oc adm policy add-role-to-user admin lab-admin -n lab-sample この後、lab-admin ユーザとして oc でログインすると、認証が通り、lab-sample プロジェクトを触れる状態になります。ロールの割り当てをせずにログインして new-project しても同じ状態になるはずです。\n$ oc login -u lab-admin Authentication required for https://api.ocp.kuro.lab:6443 (openshift) Username: lab-admin Password: Login successful. You have one project on this server: \u0026#34;lab-sample\u0026#34; Using project \u0026#34;lab-sample\u0026#34;. サンプルアプリケーションのデプロイ ここまできたらそこそこ使える状態です。\nGUI に lab-admin でログインして、デベロッパコンソールのカタログからサンプルアプリケーションをデプロイします。今回は適当に Django のヤツを選びました。\nベースイメージと GitHub のサンプルコードを使ってビルドが走り、イメージがレジストリにプッシュされ、そのイメージを使ってポッドが作成されます。\n自動でサービスとルートも作られて、無事にブラウザでアクセスできました。\nおわりに 最新の OpenShift 4.6 で、ベアメタルホストに対する IPI インストールを試しました。\n今回は物理サーバは仮想マシンで代替しましたが、ほんとうに物理サーバを使う場合でも、同様の考え方でインストールはできそうです。最初のインストールだけでなく、物理ホストの追加も相当ラクですし、ノードの停止や起動も簡単なので、比較的柔軟に構成できるでしょう。\nまた、仮想マシンに対して仮想 BMC インタフェイスを提供できると、こうした物理サーバに対する完全に自動化されたプロビジョニングも仮想環境で気軽に試せて楽しいですね。VirtualBMC for vSphere、ご活用ください。\n","date":"2020-11-24T14:57:59Z","image":"/archives/3524/img/2020-11-24-024828.png","permalink":"/archives/3524/","title":"OpenShift 4.6 のベアメタル IPI インストールを試す"},{"content":"数年前の VMworld で参考展示がされていた ESXi on ARM ですが、Flings からついに ESXi Arm Edition としてリリースされました。\nESXi-Arm Fling now available! - Arm at VMware ESXi Arm Edition | VMware Flings 現段階ではバージョンは 7.0.0（ビルド 16966451）で、機能上は 7.0 の GA 版準拠のようです。\nSupported ESXi-Arm Hardware として Raspberry Pi 4 Model B も挙げられていて、家に 4 GB の子が遊んでいた ので、入れてみました。\n期待以上に素直に動きましたが、vCLS のエージェント VM が ARM だと動かないため、vCenter Server のバージョンが 7.0 update 1 の場合はクラスタに入れられなくなる（入れられはするがエラーが出続ける）点には注意が必要です。\n必要なモノ インストールの過程を含めて、以下が必要です。\nRaspberry Pi 4 本体（メモリは 4 GB または 8 GB のみサポート） SD カード（UEFI ブート用） USB ストレージまたは iSCSI の LUN（ESXi のインストール先） USB メモリ（ESXi のインストーラの ISO を書き込んだもの。インストール作業にのみ必要） 最終的には、SD カード中の UEFI ファームウェアを使ってブートシーケンスが開始されるものの、ESXi そのものは USB ストレージか iSCSI の LUN からブートする ようになります。\nつまり、SD カードだけでなく、何らかの形で外部ストレージが必要ということですね。今回は、余っていた SSD を USB で接続して使っています。\nまた、インストール時のみ、インストーラの ISO を起動させるための USB メモリが必要です。インストーラの画面はシリアルコンソールでも取れるようですが、HDMI で素直にディスプレイをつなぐほうがシンプルですね。\n手順 大まかな流れ Flings の ESXi Arm Edition のページ で詳細なドキュメントがダウンロードできるので、基本はそちらに従います。\n大まかには、以下の流れです。\nRaspberry Pi 4 の EEPROM の最新化 SD カードの UEFI ブート用のセットアップ UEFI ファームウェアの設定 インストーラの USB メモリへの書き込み インストーラの起動とインストール Raspberry Pi 4 の EEPROM の最新化 Raspberry Pi Imager を使って、Raspberry Pi OS を SD カードに書き込み、\nその SD カードでブートして、適当に初期設定をしてネットワークにつながるようにしたら、EEPROM のアップデートを確認します。\nsudo rpi-eeprom-update OS 起動直後の初期セットアップウィザードの中でネットワーク設定とアップデートを実行した場合は、その段階で最新化されるようです。ネットワークを後から設定した場合など、最新化されていなければ、手動でアップデートをします。\nsudo rpi-eeprom-update -a sudo reboot 最新化されたら、シャットダウンします。一瞬でしたが、ここまでで Raspberry Pi OS の出番は終了です。\nsudo shutdown -h now SD カードの UEFI ブート用のセットアップ SD カードを FAT32 でフォーマットして、次のモノを突っ込みます。\nRaspberry Pi Firmware master ブランチを丸ごと ZIP でダウンロード して 解凍した boot フォルダ内の、kernel*.img 以外 のすべてのファイルとフォルダを SD カードの直下にコピー Raspberry Pi 4 UEFI Firmware v1.20 をダウンロード して 解凍したすべてのファイルとフォルダを SD カードの直下にコピー（同名ファイルは 上書き） さらに、使う RPi 4 のメモリが 4 GB の場合は、SD カード直下の config.txt に次の一行を追記します。\ngpu_mem=16 最終的にこんな感じになります。この例では、U:\\ が SD カードです。\nUEFI ファームウェアの設定 できた SD カードで Raspberry Pi を起動し、Raspberry Pi のロゴが出たら ESC キーで UEFI の設定画面に入ります。\nUEFI の設定画面が出たら、[Device Manager] \u0026gt; [Raspberry Pi Configuration] \u0026gt; [Advanced Configuration] \u0026gt; [Limit RAM to 3GB] を [Disabled] に設定し、F10 で保存します。\n最低限はこれだけですが、これ以外にも、画面の解像度を変えたい場合や、シリアルコンソールを使う場合などは追加で設定が必要です。ガイドに書いてあるので適宜参照して修正します。\n終わったら、ESC で最初の画面まで戻り、[Continue] して Enter キーで設定を確定します。\nインストーラの USB メモリへの書き込み インストーラを用意します。Etcher でも Rufus でもお好みのモノで ESXi Arm Edition の ISO ファイルを USB メモリに書き込むだけです。\nインストーラの起動とインストール インストーラを書き込んだ USB メモリと、インストール先になる USB ストレージを Raspberry Pi に接続します。\n起動させたら、Raspberry Pi のロゴ画面で ESC を押下して、再度 UEFI の設定画面に入ります。デフォルトのブートオーダでは、USB デバイスの優先順位は最下位なので、この段階で修正します（ガイドだと後回しですが……）。\n[Boot Maintenance Manager] \u0026gt; [Boot Order] で、第一位をインストーラの USB メモリ、第二位をインストール先の USB ストレージにして Commit Changes and Exit で保存し、ESC で画面を戻って [Continue] から再起動します。\n今回は、ESXi のインストーラが起動してきたら、Shift + O を押下して、autoPartitionOSDataSize=8192 を起動オプションに追記しました。\nこれは、なるべく USB ストレージ内に作れるデータストアのサイズを大きくしたいからです。説明は割愛しますが、ESXi のインストール先のストレージに作成されるパーティションのレイアウトが 7.0 から大きく変わっており、デフォルトではストレージのサイズ次第では最大で 138 GB が ESXi 用に取られる設計になっています（つまり、データストアサイズはその残り）。\n関連ドキュメントは以下あたりです。\nESXi Hardware Requirements Changing the default size of the ESX-OSData volume in ESXi 7.0 今回は家で余っていた 240 GB の SSD にインストールしようとしており、そうするとローカルデータストアのサイズがけっこうちんまりしてしまうので、先述のオプションを足すことにしました。\nこのあとは、普通の ESXi のインストールと同じです。\n完了後、インストーラの USB メモリを抜いて再起動すれば、素直に ESXi が上がってきます。通常通りネットワークやホスト名などを設定して、完成です。\n観察 Host Client につないでみます。\nちゃんと Raspberry Pi だし、ARM ですね。4 GB の子に突っ込んだので、空きメモリが 2.8 GB と大変心もとない感じになっていますが……。\nvCenter Server にも登録できました。時刻がズレすぎていると証明書のいろいろで失敗することがある（Unable to push signed certificate to host なエラーが出る）ので、そこだけ登録前に Host Client で直したほうがよさそうです。\nなお、vCenter Server のバージョンが 7.0 update 1 の場合、現時点ではホストクラスタには追加しない ほうが良さそうです。7.0 update 1 からホストクラスタ内のホストに自動で vCLS のエージェント VM がデプロイされますが、これが ARM に対応していない ためです。\n実際、ホストクラスタに ARM な ESXi を追加すると、vCLS が存在しないエラーが上がり続け、内部的にデプロイし続けるからか、ファイルの削除タスクが繰り返される 状態になります。\n仮想マシンの動作 仮想マシンも、スペックが許す範囲で通常通り動作します。注意点は、\nゲスト OS も ARM 版を入れる必要がある点 VMware Tools（Open VM Tools）に ARM 版がない場合はソースコードからのコンパイルが必要になる点 あたりです。\nなお、VMware Tools は、Photon OS の 3.0 であればリポジトリから降ってくるモノが ARM でも動作するようで、普通に tdnf install open-vm-tools するだけでよいようです（ただし systemd が古い場合は最新化しないと設定をいじる必要あり）。\nInstalling VMware Tools on Photon OS for ESXi-Arm というわけで素の Photon を入れて tdnf で Open VM Tools を入れたところ、すんなりうごきました。\nシリアルコンソール GND と TXD（GPIO14）と RXD（GPIO15）をつなぐと DCUI がシリアルで取れます。ターミナル側は 115200 の 8N1 です。\nルックアンドフィールはほぼそのままです。おなじみの画面ではありますが、シリアルで触れることそれ自体がなかなか新鮮でおもしろいですね。\nESXi Shell も Troubleshooting Options から有効化すると取れますが、切り替えはいつもの Alt + F1 や Alt + F2 ではなく、Ctrl + G \u0026gt; Ctrl + B \u0026gt; 3 や Ctrl + G \u0026gt; Ctrl + B \u0026gt; 2 \u0026gt; Enter など、専用の操作が必要なようです。ガイドに記載があります。\nまとめ 今回は Raspberry Pi というミニマムなモノに載せましたが、RPi で動くというより、本質的には ARM アーキテクチャで動作することそれ自体 がキモなわけです。\n現時点では Flings でのリリースなので本番利用ができるものではありませんが、最近では Ampere eMAG をはじめサーバ向けのプロセッサでも ARM は盛り上がってきているようですし、ESXi Arm Edition のサポートハードウェア でもサーバ製品は挙げられており、将来的な用途の拡大は充分に見込めそうですね。\n商用目的でなくても、すでに Raspberry Pi 上の ESXi を 2 ノードの vSAN クラスタの Witness ノード として使う例 や オートメーション用の環境として使う例 も見られ、NUC よりもさらに手軽なラボ環境としていろいろな可能性がありそうです。\nあるいは、将来的に vCLS の役割が増えて、かつそれが ARM でも動くようになれば、vSphere で必要なエージェント類を動作させる廉価なプラットフォームとして使えるようになる可能性もありますし、高可用性の担保が難しかった IoT のエッジ環境でも vSphere HA などでの可用性向上が見込めるのかもしれません。\nそんな感じです。\n","date":"2020-10-10T13:24:48Z","image":"/archives/3491/img/image-201.png","permalink":"/archives/3491/","title":"Raspberry Pi 4 Model B で ESXi 7.0 を動かす"},{"content":"自宅の第 8 世代の NUC（NUC8i5BEK）で動いている ESXi 7.0b を、出たばかりの 7.0 update 1 にアップグレードしようとしたら、ドライバまわりで死んだので、最終的にどうにかするまでを含めて作業内容を書いておきます。\nまとめると、\nNUC 8 では、ESXi 7.0 U1 から、組み込みのドライバではオンボード NIC を認識できなくなった 7.0 U1 のベースイメージを使いはするが、NIC のドライバ ne1000 だけは 7.0b のドライバのままにする必要がある な感じです。\n今回は、vLCM のイメージ管理を使っていた環境だったので、\nイメージ管理からベースライン管理に戻して Image Builder を使ってカスタムイメージプロファイルを作って 作ったイメージプロファイルをベースライン化して適用した ことでアップグレードを行いました。\nなお、vCenter Server は ESXi の前に 7.0 update 1 にしていますが、VAMI からすんなりできて特別なこともなかったので、特に触れていません。\nダメだった例： vLCM のイメージ管理を使う 経緯 これまで、自宅の vSphere 7 環境の ESXi のバージョン管理には、vSphere 7 らしく vSphere Lifecycle Manager (vLCM) の イメージ管理 を使っていました（vLCM のイメージ管理を使って ESXi を 6.7 から 7.0 化してその後に 7.0b 化する様子は、別のエントリでも紹介 しています）。\nそんなわけで、今回も、vSphere 7 らしく世界最速でスマートにアップグレードすべく、vLCM から 7.0 update 1 が見えるようになった直後に更新を（ひとまず一台だけ）走らせたんですが、再起動を開始したあとに上がってこなくなりました。\nダメになっている様子 コンソールを見ると、見たくなかった No compatible network adapter が出て死んでいます。\nNUC8i5BEK に積んでいる NIC は Intel の Ethernet Connection (6) I219-V という（デバイス ID が 15BE の）ヤツで、これ自体は HCL にも 7.0 U1 でのサポートが載っています。対応するドライバは ne1000 で、死んでいる中を見ると入っているようにも見えますが、うまく使えていません。\n実は、第 10 世代の NUC では ESXi 7.x が NIC をうまく認識せず、ワークアラウンドとして 6.7 の頃のドライバを入れなおす必要があるのはよく知られた話でした。第 8 世代では 7.0b までは問題なく動いていたものの、つまり、7.0 U1 になって同種の問題が顕在化したことになります。というわけで、回避するには、\n7.0 U1 にアップグレードするが NIC のドライバ ne1000 だけは 7.0b のものを使う必要がある ということです。\n根本的に、NUC そのものは HCL に載っておらず、サポートされていない ので、まあ、何が起きても仕方がないですねという感じではあります。\n余談： 復旧まで アップグレードに失敗してネットワーク疎通が失われると、ネットワーク経由の操作ができなくなるので、DCUI や物理的な電源ボタンを使って再起動する必要があります。\n幸い、再起動するとイイ感じにロールバックされて元通りになる…… のですが、きれいに戻り切らず、vLCM 上でのコンプライアンスステータスが不明になってしまいました。\n見てみると、tools-light パッケージだけが最新化されたままロールバックされておらず、何らかの不整合を起こしている模様です。7.0b 時代の同パッケージを手でインストール（ダウングレード）すると元通りになりました。\n[root@kuro-esxi02:~] esxcli software vib install -n tools-light:11.1.0.16036546-16321839 -d https://hostupdate.vmware.com/software/VUM/PRODUCTION/main/vmw-depot-index.xml Installation Result Message: Operation finished successfully. Reboot Required: false VIBs Installed: VMware_locker_tools-light_11.1.0.16036546-16321839 VIBs Removed: VMware_locker_tools-light_11.1.1.16303738-16850804 VIBs Skipped: VMware Tools 関連のパッケージは ESXi に同梱されずに別で提供されるようになってきていることもありますので、もしかするとこのコンポーネントだけはロールバックの対象外だとか、なにか事情があるのかもですね。\n本題： 7.0 U1 化 作戦 7.0 U1 のベースイメージに 7.0b のドライバを入れればよいとアタリが付きました。が、問題が出てきます。\nvLCM のイメージ管理では、ne1000 のようにベースイメージに含まれてしまっているコンポーネントの一部だけバージョンを変える、みたいな細かいカスタマイズが難しそう イメージ管理に設定したクラスタは、ベースライン管理に戻せない そんなわけで、\nクラスタをいちど解体して作り直し、vLCM での管理手法をベースライン管理に戻す ne1000 だけバージョンを落としたカスタムイメージを作って、それをベースラインにしてアップグレードする ことにします。つまり、昔ながらの VUM のやり方でやりましょうということですね。悲しい。\nベースライン管理への復元 vLCM でイメージ管理を構成してしまったホストクラスタは、ベースライン管理には戻せないので、いちどクラスタを解体する必要があります。\nvDS を維持したままにしたかったので、ESXi を切断して削除して再登録する雑な方法ではなく、お行儀よく 1 台ずつメンテナンスモードにして逃がし、vMotion なども駆使して、新しいホストクラスタに VM を止めずに再構成しました。\nこれはそんなに難しくないですね。本番環境だといろいろアレですが、自宅だしアレなのでヨシです。\nカスタムイメージプロファイルの作成 ここから、NUC 8 に合わせた 7.0 U1 のイメージを作成していきます。\n作成には、vCenter Server に組み込みの Auto Deploy の付帯機能である Image Builder を使います。デフォルトではサービスが停止しているので、まずは Auto Deploy の画面の 設定 タブから、IMAGE BUILDER サービスの有効化 を行います。もしくは、VAMI で Image Builder サービス を起動させても同じです。\n起動させると Image Builder が使えるようになるので、まずは ソフトウェアデポ タブで、ベースになるイメージを取得するべく、適当な名前で 公式のオンラインソフトウェアデポを追加 します。\nName VMware online software depot URL https://hostupdate.vmware.com/software/VUM/PRODUCTION/main/vmw-depot-index.xml さらに、適当な名前で空のカスタムソフトウェアデポを作成します。\n公式のオンラインデポの中から、ESXi 7.0 U1 のイメージ ESXi-7.0.1-16850804-standard を選択して クローン作成 し、名前を適当に変えて、複製先の ソフトウェアデポ として先ほど作成したカスタムソフトウェアデポを選択して進みます。\n次の画面で、パッケージの一覧が表示されます。この一覧は、オンラインソフトウェアデポに含まれる全パッケージの全バージョン で、この段階で、ESXi 7.0 U1 のベースイメージを構成する 74 のパッケージが選択済み になっています。\nここで、名前 フィールドを ne1000 でフィルタすると、7.0 U1 に組み込まれたバージョンである 0.8.4-11vmw.701.0.0.16850804 が選択されていることがわかります。\nNUC 8 で動くのはこのひとつ前の 0.8.4-10vmw.700.1.0.15843807 なので、ここで、こちらのみが選択された状態に修正します。\nその後、画面を進めて 完了 すると、カスタムソフトウェアデポに、カスタマイズ済みのイメージプロファイルができあがっているはずです。\nソフトウェアパッケージの表示 では、ne1000 だけが古い状態になっていることが確認できます。\nあとは、このプロファイルを エクスポート で ISO ファイルとして保存するだけです。作成が終わると、エクスポート が ダウンロード に代わるので、ダウンロードするか、リンク先の URL をコピーします。\nベースラインの作成と適用 ISO がダウンロードできた（か、URL がコピーできた）ら、vLCM の画面に移動して、インポートされた ISO タブの ISO のインポート から、先ほど作成された ISO ファイルをアップロード（か、コピーしておいた URL を指定）し、インポートさせます。\nあとは、この ISO を選択して 新規ベースライン でベースライン化して、\nクラスタに 添付 し、\nコンプライアンスの確認 ののちに 修正 するだけです！\nまたコケたら怖いので DRS ナシで一台ずつやります。\n再起動後、懸念だった NIC も正しく認識され、修正処理が完走できました。残りも順次適用して、7.0 U1 化の完了です。\nなお、この方法で適用したあとに再度コンプライアンスを確認すると、重要なホストパッチ のベースラインが添付されている場合は、非準拠扱い になりますが、無視する必要があります。これは、あえてアップグレードしていない ne1000 のパッケージが 7.0 U1 相当に上がっていないことによるものです。これを 修正してしまうと再び NIC が認識されなくなる ので、放置するしか手がありません。\n後始末 Image Builder はもう使わないので、VAMI から Image Builder サービス を選択して停止させます。起動させるときは Auto Deploy の 設定 タブからできましたが、停止はできないようで……。\nまとめ vLCM のイメージ管理は使えなくなりましたが、7.0 U1 化を無事に完了できました。HCL に載っていない非サポートハードウェアだとこういうことがたまにあるので、仕方なしですね。\nImage Builder は、7.0 が出たばかりの頃はバグで新しいイメージプロファイルが作れなかったようですが、無事に使えるようで一安心です。\nまた、Image Builder とオンラインソフトウェアデポを使った今回の手順は、My VMware から自分で何かをダウンロードしなくてよい ところにもあります。というのも、VMUG Advantage だと、まだ 7.0 U1 がダウンロードできないからです。\n今回は NUC 8 の例でしたが、今後の別の NUC で同等のことが発生した場合には、同等の手法で解決できる可能性があります。\n","date":"2020-10-08T16:17:40Z","image":"/archives/3453/img/image-198.png","permalink":"/archives/3453/","title":"NUC 8 の ESXi を 7.0b から 7.0 update 1 にアップグレードする"},{"content":"ざっくり紹介 いろいろあって作りました。\nVirtualBMC for vSphere (vbmc4vsphere) GitHub（リポジトリ） GitHub Wiki（使い方ガイド） PyPi（プロジェクトページ） これを使うと、vSphere 環境上の仮想マシンを、IPMI （ipmitool など）で制御できます。\n物理サーバとまったく同じプロトコルが（もちろん全部ではないですが）仮想マシンに対して使えるようになるため、単に ipmitool による手動操作だけでなく、バックエンドで IPMI を必要とする、または IPMI があれば動かせる物理サーバ向け機能も仮想環境で動作させられる 可能性がある点が大きなメリットです。\n具体的には、\nipmitool での手動操作（電源制御、起動デバイスの変更） vCenter Server からの ESXi のパワーオン vSphere DPM（Distributed Power Management）による自動電源制御 oVirt など他の仮想環境管理ツールの自動電源制御 OpenShift の Baremetal IPI など、物理サーバに対する自動プロビジョニング機構 が、vSphere の仮想環境内で実際に動作させられる可能性があります。\nもちろん、物理環境を完全にエミュレートできるわけではないため、完全な代替とは言えず種々の注意は要しますが、前述のものはいずれも動作確認済みです。\n本エントリでは、仕組みと簡単な使い方を紹介します。\n仕組み OpenStack 傘下のプロジェクトで、IPMI で libvirt 管理下の KVM 仮想マシンを制御できる VirtualBMC というものがあり、今回の VirtualBMC for vSphere は、それの libvirt 部分を vSphere 向けに修正したうえで機能追加したものです。\nよって、仕組みは前身の VirtualBMC に準拠したものですが、とはいえごく単純（というかこれ以外にないともいう）で、IPMI コマンドを外部から受け付けるデーモンが、受け付けたコマンドと同等の処理を vCenter Server に API でリクエストしなおすだけです。\nなお、IPMI の世界では、コマンドを受け付けた BMC と操作対象の物理サーバは一対一である前提なので、プロトコルレベルでは操作対象を指定する仕組みをそもそも持っていません。すなわち、操作対象の仮想マシンは、物理サーバの IPMI 操作と同様に、IPMI の投げ先の IP アドレスとポート番号で厳密に指定できる必要があります。\nこのため、VirtualBMC では、操作対象の仮想マシンごとに別の仮想 BMC インスタンスを同時に起動できるようになっていて、かつそのインスタンスはインスタンスごとにホストの任意の IP アドレスや任意のポートにバインドできるようになっています。\n簡単な使い方 詳しくは別で書いてあるので併せてどうぞ。\nGit リポジトリ Wiki セットアップ Python 3.x と pip が動く環境で pip install vbmc4vsphere するとインストールできます。\npython -m pip install vbmc4vsphere インストールしたら、まずはデーモンを起動します。バックグラウンドで起動させる場合はオプション無し、フォアグラウンドで動かして出力（ログ）を見たい場合は --foreground を付けます。\nvsbmcd --foreground その後、vsbmc add で新しい仮想 BMC を作成します。この時に、仮想マシン名や待ち受ける IP アドレス（--address）とポート番号（--port）、vCenter Server の認証情報などを突っ込みます。\nvsbmc add lab-vesxi01 --port 6230 --viserver 192.168.0.1 --viserver-username vsbmc@vsphere.local --viserver-password my-secure-password 制御したい仮想マシンの数だけこれを繰り返します。上記の例ではポート番号だけを指定していて、その場合はホストのすべての IP アドレス（0.0.0.0）で待ち受けるように設定されます。\nなお、ポート番号として 1024 以下を指定したときは、デーモンを管理者権限（sudo するか root ユーザ）で起動していないと、この後の起動でコケるので注意です。\n作成できたら、仮想 BMC を起動させます。\nvsbmc start lab-vesxi01 登録情報や状態は、vsbmc list や vsbmc show で確認できます。\n$ vsbmc list +-------------+---------+---------+------+ | VM name | Status | Address | Port | +-------------+---------+---------+------+ | lab-vesxi01 | running | :: | 6230 | +-------------+---------+---------+------+ $ vsbmc show lab-vesxi01 +-------------------+---------------------+ | Property | Value | +-------------------+---------------------+ | active | False | | address | :: | | password | *** | | port | 6230 | | status | running | | username | admin | | viserver | 192.168.0.1 | | viserver_password | *** | | viserver_username | vsbmc@vsphere.local | | vm_name | lab-vesxi01 | +-------------------+---------------------+ 止めるときは vsbmc stop です。\nvsbmc stop lab-vesxi01 動作確認 vsbmc start して状態が running になったら、IPMI コマンドでの操作ができるようになるはずです。\nユーザ名とパスワードはデフォルトでは admin、password です。変更したい場合は、先の vsbmc add するときの引数で指定できます（vsbmc add --help でヘルプ参照）。\n$ ipmitool -I lanplus -H 192.168.0.100 -p 6230 -U admin -P password chassis power on Chassis Power Control: Up/On $ ipmitool -I lanplus -H 192.168.0.100 -p 6230 -U admin -P password chassis power status Chassis Power is on $ ipmitool -I lanplus -H 192.168.0.100 -p 6230 -U admin -P password chassis power soft Chassis Power Control: Soft $ ipmitool -I lanplus -H 192.168.0.100 -p 6231 -U admin -P password chassis power reset Chassis Power Control: Reset $ ipmitool -I lanplus -H 192.168.0.100 -p 6231 -U admin -P password chassis bootdev pxe Set Boot Device to pxe $ ipmitool -I lanplus -H 192.168.0.100 -p 6231 -U admin -P password chassis bootparam get 5 Boot parameter version: 1 Boot parameter 5 is valid/unlocked Boot parameter data: 8004000000 Boot Flags : - Boot Flag Valid - Options apply to only next boot - BIOS PC Compatible (legacy) boot - Boot Device Selector : Force PXE - Console Redirection control : System Default - BIOS verbosity : Console redirection occurs per BIOS configuration setting (default) - BIOS Mux Control Override : BIOS uses recommended setting of the mux at the end of POST コンテナ環境で動作させる場合 コンテナでも動作することは確認できていて、使い方は Wiki に書いてあります。\nContainerized VirtualBMC for vSphere コンテナイメージも Docker Compose 用のファイルも用意済みなので、慣れている方はこちらの方が環境を汚さないし良いでしょう。ポートのバインドがデーモンレベルとコンテナレベルの二か所で必要になってちょっと複雑なので、そこだけ注意です。\n利用上の注意 根本的には、本番利用は当然想定していないので、テストとか遊びとか、そういう感じで使ってください。\nまた、vsbmc add で指定したときの情報は、~/.vsbmc/\u0026lt;仮想マシン名\u0026gt;/config に 平文で 保存されます。これには vCenter Server のパスワードも含む ため、運用には注意が必要です。\nユースケース 試したいくつかの使い方を紹介します。\nネステッド ESXi のパワーオン VirtualBMC for vSphere は、vCenter Server に ESXi の BMC として登録できます。\nこのため、ネステッド ESXi の BMC として VirtualBMC for vSphere を登録する と、物理 ESXi と同じように、ESXi のパワーオン（スタンバイからの復帰）が vSphere Client から行える ようになります。\nvCenter Server へ登録する BMC は、待ち受けポートが必ず 623 番でなくてはならず、また、誤登録防止のために MAC アドレスも必要です。これらを考慮したガイドを Wiki に載せています。\nUse with Nested ESXi and vCenter Server ネステッド環境で vSphere DPM を有効化 前述のように、VirtualBMC for vSphere はネステッド ESXi の BMC として登録できるので、つまり、ネステッド ESXi で構成されたホストクラスタで、DPM（Distrubuted Power Management）も有効化できます。\nvSphere DPM は、これまで物理サーバなしには試すのが難しかったのですが、これで比較的簡単に動かせるようになります。\n上のアニメーションは、ネステッド ESXi で構成されたホストクラスタで vSphere DPM を有効化した結果、\nESXi のスタンバイからの自動的な復帰 復帰後の自動負荷平準化（vMotion） が動作している様子です。同様に、動作している仮想マシンを減らすと、ESXi が自動的にスタンバイモードに移行する様子も観察できます。\nなお、vSphere DPM をネステッド環境で動作させる上では、vSphere DPM のコスト計算が正しくない 点に注意が必要です。\nvSphere DPM は、ESXi のスタンバイ化やパワーオンの必要性を評価するためにさまざまなメトリックを利用しており、それには ESXi 自身が自分自身のハードウェアから直接取得する情報 が含まれます。\nしかしながら、ネステッド ESXi が使っている 仮想ハードウェアは、それらの情報を提供しません。例えば、ネステッド ESXi の消費電力は常にゼロとして評価されます。そして、ハードウェアから直接でしか情報を取得しないので、そこに 仮想 BMC が介在する余地はありません。\n結果として、vSphere DPM のコストの評価結果が正しくなく、期待した推奨が提案されない 場合があります。本気の検証には向かないので、あくまで動作の雰囲気をつかめるくらいと捉えるのがよいでしょう。\n構成方法は、前述のガイドに載せています。\nUse with Nested ESXi and vCenter Server oVirt でネステッド KVM のフェンスエージェントとして登録 oVirt では、ノードの BMC をフェンスエージェントとして登録すると、可用性の向上が実現できます。具体的には、ホストで障害が検出されると、スプリットブレインの発生有無の確認のためにホストの電源状態を確認したり、電源がオフであることが分かった場合には電源の投入などが試みられます。\nVirtualBMC for vSphere は、vSphere 環境の仮想マシンとして構成した KVM ホストの BMC として oVirt に登録できます。\n構成方法は、Wiki に載せています。\nUse with Nested KVM and oVirt OpenShift の自動ベアメタルインストール（IPI）を仮想マシンで OpenShift をまっさらな環境にほぼ全自動でインストールする方法に IPI と呼ばれる手段があり、そのうち まっさらな物理サーバに全自動でインストールする方法が、ベアメタル IPI（Baremetal IPI）です。\n名前の通り、本来は物理的に BMC を積んだベアメタルサーバ（物理サーバ）に対して自動インストールを行うもので、その過程では IPMI での電源制御やブートデバイス指定（PXE、ローカルディスクなど）が駆使されます。\nVirtualBMC for vSphere を使うと、これを仮想マシンに対して行えるようになります。\n上の画像は、実際に仮想マシンに対してベアメタル IPI の手順で構成した OpenShift 環境です。実体は仮想マシンですが、OpenShift としては Bare Metal Hosts として登録されています。Management Address に表示されている先が、VirtualBMC for vSphere です。\nもちろん、OpenShift としてはそもそも vSphere 環境向けのインストール手段も用意されているので、OpenShift 環境が欲しいだけならそれでもよいわけですが、どちらかというとこのユースケースは 物理サーバのプロビジョニング手順を仮想環境で気軽に試せる ことがメリットかと思います。\nサンプルの設定例などは Wiki に載せています。\nInstall OpenShift in vSphere environment using the Baremetal IPI procedure 余談： 開発のきっかけ もともとは vSphere DPM がどうのこうのなどはまったく考えておらず、oVirt を動かして遊びたくなっただけでした。\nその時は、ネステッド ESXi と同じく、vSphere の上にネステッド KVM として組めばいいやと考えたわけですが、ドキュメントを読んでいたところこんな記述を見つけました。\nPower management must be configured for the hosts running the highly available virtual machines. Administration Tasks | oVirt\r可用性を高くしたいなら電源管理を構成にしなさいよ、とのことです。しなくても HA 自体は機能するようですが、こうなるとせっかくなので試したくなるわけです。\n電源管理というのは、oVirt 的には フェンスエージェントを構成して oVirt がホストの電源状態を確認・制御できるようにすること を意味します。これにはつまり何らかの BMC が必要です。\nエージェントに ipmilan が選べるので、IPMI が喋れさえすればよさそうですが、そもそもネステッドで組もうとしているので、vCenter Server にも ESXi にも仮想マシンの IPMI 操作を受け付ける機能はないし、どう考えても無理です。\nとはいえ、IPMI over LAN なんてただの UDP パケットなわけだし、世の中のだれかが仮想 IPMI ゲートウェイ的なものをすでに作っていることはじゅうぶんに期待できます。が、調べると、残念ながら vSphere 向けは皆無でした。\n先行事例は、以下のような具合です。\npyghmi OpenStack 傘下の Python 製サーバ管理用モジュール IPMI のサーバ側としての初期ネゴシエーション、セッション管理の仕組みや、物理サーバの BMC をエミュレートするクラスが実装済み VirtualBMC OpenStack 傘下の仮想 BMC IPMI コマンドを受けて libvirt ドライバ経由で仮想マシンを制御できる pyghmi をベースにしている mushorg/conpot 産業用の監視・制御・データ収集システム向けのハニーポット IPMI も受け付けてフェイクの返事を返すようにできている pyghmi をベースにしている rhtyd/ipmisim 上記 conpot から IPMI 部分だけを切り出した IPMI のシミュレータ 調べた範囲では VirtualBMC がいちばんぼくの求めるモノに近く、libvirt 向けの処理を pyvmomi で vSphere 向けに書き直せばどうにかなりそうだったので、ライセンスも APL 2.0 ですし、これを基に拡張していこうというのが今回の開発のきっかけでした。\nVirtualBMC ですでに書かれていた処理を vSphere 向けに置き換えるところまではさくさくだったのですが、vSphere DPM での利用を目論んで vCenter Server に BMC として登録できるようにする部分が厄介でした。\n結局、VirtualBMC（やその基の pyghmi）でも対応していないパケットのフォーマットや IPMI のコマンドがいくつも必要になってしまい、それらを処理する部分を完全に自製することになりました。\nパケットをキャプチャして、IPMI と ASF の仕様書を片手に vCenter Server からの要求を読み解き、応答パケットを仕様書に従って組み立てて投げる、的な作業の繰り返しです。\nすごくおもしろかったです。\n本エントリ執筆時点で、累計ダウンロード数がすでに 1,300 を超えており、ニッチながらそこそこの需要はあったのかもしれない感触を得られています。ご活用ください。\n追記： バージョン 0.1.0 で、コマンド名を vsbmcd と vsbmc に変更しました。これまでフォーク元の本家 VirtualBMC からコマンド名を変えておらず実は競合する状態を放置したままでしたが、これを解消させる目的です。併せて、本文中のコマンドも修正しました。\n","date":"2020-09-13T16:14:19Z","image":"/archives/3433/img/image-175.png","permalink":"/archives/3433/","title":"vSphere 上の仮想マシンを IPMI で操作する： VirtualBMC for vSphere (vbmc4vsphere)"},{"content":"追記： 7.0.0d で修正済み 本件の修正を含んだ 7.0.0d がリリースされました。\nVMware vCenter Server 7.0.0d Release Notes Resolved Issues にも本件と思われる問題が記載されています。\nAfter an upgrade to vCenter Server 7.0.0c, you see high CPU usage After you upgrade your vCenter Server system to vCenter Server 7.0.0c, CPU usage continuously stays high. On a single core, CPU usage might spike up to 100% for hours. The Workload Control Plane service causes the issue, even if you do not have Workload Management enabled in your environment. This issue is resolved in this release. VMware vCenter Server 7.0.0d Release Notes\r手元の環境でも、ワークアラウンドを元に戻して事象を再現させた状態で 7.0.0d にアップデートし、事象が再現しなくなったことが確認できました。\nTL;DR vCenter Server のワークロード制御プレーン（Workload Control Plane, WCP）サービスの既知の問題 今後リリースされる Express Patch 1（EP1）で修正される予定 以下の二つの条件が許容できるなら、問題のサービスを止めれば抑制できる vSphere with Kubernetes 関連の機能が使えなくなる ホストをメンテナンスモードにできなくなる 上記の二つの条件が許容できない場合は、EP1 のリリースまで 7.0.0b のままにするか、CPU の高騰を許容する必要がある 7.0.0c 化後、突然の CPU 使用率の高騰 vCenter Server を 7.0.0c にしてしばらく放っておいたら、どうにも空冷ファンがいつもよりもうるさいままで、おかしいなあ、みたいな。見たらこうなっていました。\ntop で SHIFT + P するとこんな。\n/usr/lib/vmware-wcp/wcpsvc なるプロセスが張り付いていました。\ntop - 01:00:58 up 1:43, 1 user, load average: 1.49, 1.73, 1.72 Tasks: 290 total, 1 running, 289 sleeping, 0 stopped, 0 zombie %Cpu0 : 99.3/0.7 100[||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||] %Cpu1 : 4.7/3.4 8[|||||||| ] GiB Mem : 73.5/11.7 [ ] GiB Swap: 0.3/26.0 [ ] PID USER PR NI VIRT RES %CPU %MEM TIME+ S COMMAND 29354 root 20 0 505.5m 84.2m 98.7 0.7 81:36.55 S /usr/lib/vmware-wcp/wcpsvc --port 8920 --logfile /var/log/vmware/wcp/wcpsvc.l+ 1196 root 20 0 2495.5m 30.6m 2.6 0.3 4:39.03 S /usr/java/jre-vmware/bin/java -XX:+UseStringDeduplication -XX:+OptimizeString+ 1318 vdtc 20 0 181.4m 3.2m 0.7 0.0 0:32.24 S /usr/lib/vmware-vdtc/vdtc 7965 vsphere+ 20 0 2683.7m 851.8m 0.7 7.1 1:53.38 S /usr/java/jre-vmware/bin/vsphere-ui.launcher -Xmx597m -XX:CompressedClassSpac+ 10455 root 20 0 133.3m 14.3m 0.7 0.1 0:21.22 S /usr/lib/vmware-vmon/vapi/vmon-vapi-provider -p 8900 -l info 1 root 20 0 92.6m 7.7m 0.0 0.1 0:01.54 S /lib/systemd/systemd --switched-root --system --deserialize 17 2 root 20 0 0.0m 0.0m 0.0 0.0 0:00.00 S [kthreadd] ... 原因と暫定対策 調べると、既知の問題のようです。\nHigh CPU after vCenter update 7.0b -\u0026gt; 7.0c VMware vCenter 7.0.0c high CPU usage vSphere with Kubernetes 関係のサービスのようですね。VAMI から止められるとのことなので、止めてみます。\n日本語だと ワークロード制御プレーン、英語だと Workload Control Plane のようです。選択していちばん上の [停止] を押すだけ。シェルで vmon-cli -k wcp でもよいですね。\nきれいにおさまりました。\nなお、この方法でのサービス停止は一時的なものなので、vCenter Server が再起動するとこの問題は再発するようです。\nつまり、再起動後もこのサービスを止めたままにするには、シェルに入って、\nroot@record [ ~ ]# vmon-cli -s wcp Name: wcp Starttype: AUTOMATIC RunState: STOPPED RunAsUser: root CurrentRunStateDuration(ms): 885398 HealthState: UNHEALTHY FailStop: FALSE MainProcessId: N/A root@record [ ~ ]# vmon-cli -S DISABLED -U wcp Completed Service State Update request. root@record [ ~ ]# vmon-cli -s wcp Name: wcp Starttype: DISABLED RunState: STOPPED RunAsUser: root CurrentRunStateDuration(ms): 910637 HealthState: UNHEALTHY FailStop: FALSE な感じで、自動起動をオフにする必要があります。オンに戻すときは DISABLED を指定しているところで AUTOMATIC にします。\n暫定対策の副作用 vSphere with Kubernetes 関連のサービスなので、K8s 関連の機能は動かなくなりそうです（どこまで動かなくなるのかは試していません……）。\nまた、それだけでなく、先のリンク先には、WCP を止めると メンテナンスモードにできなくなる と書いてあります。試してみたら、確かに以下のエラーでメンテナンスモードへの切り替えに失敗しました。\nその操作は、現在の状態では実行できません。 ノードで名前空間のメンテナンス モードへの切り替えに失敗したため、ホストをメンテナンス モードに切り替えることができません。\nメンテナンスモードにできないとなると、vLCM（旧 VUM）でのアップデートの時などに困りそうです。となると、必要な時は一時的にサービスを上げるのがよいのかもですね。もしくはおとなしく 7.0.0b の頃のバックアップをリストアするか……。\n恒久対策 コミュニティでのやりとりしか情報がないですが、次に出る EP1（Express Patch 1）での修正が予定されているようです。待ちましょう。わりと近いうちに出そうな気はするけれど、どうでしょう。\n修正されるまでは、7.0.0c の適用は見送る（7.0.0b のままにしておく）判断もアリになりそうです。\n","date":"2020-08-20T17:27:51Z","image":"/archives/3407/img/image-170.png","permalink":"/archives/3407/","title":"vCenter Server 7.0.0c で CPU 使用率が高騰する"},{"content":"はじめに vCSA には、以前から vROps Client Plugin がバンドルされており、vSphere Client から簡単に vRealize Operations Manager のインスタンスをデプロイできました。\n7.0 でもこれは健在なので、おうち vROps の実現を目指します。vROps のデプロイは OVF から手作りしたとしてもそもそもそこまで難しいものでもないのですが、この方法ではより一層簡単です。\n……と思っていたらハマったので、その解決策も含んだお話です。\nvROps Client Plugin でのデプロイ プラグインの確認 vSphere Client の [管理] \u0026gt; [クライアントプラグイン] の画面では、VMware vROps Client Plugin の存在が確認できます。このプラグインにより、vROps の vSphere Client への統合が実現されていて、今回のようなデプロイや、簡単なダッシュボードの閲覧が可能です。\n手元の環境では、プラグインのバージョンは、vSphere Client のバージョンと同じ 7.0.0.10400（7.0.0b）でした。\n参考ドキュメント vRealize Operations Manager それ自体は、ドキュメントは充実しています。プラグインを使わない構築手順などもいつも通り用意されています。\nvRealize Operations Manager Documentation ただ、vROps Clinet Plugin については、7.0 版のドキュメントがまだなさそう？ です。それでも 6.7 版はあり、だいたい一緒なので、ほとんどそのまま参考にできそうです。\nvRealize Operations Manager Plugin in vCenter Server この方式での制約 vROps 自体の最新は 8.1 ですが、今回の方式では、執筆時点では 8.0.0（vRealize-Operations-Manager-Appliance-8.0.0.14857692_OVF10.ovf）が降ってきました。必ずしも最新が降ってくるということではなく、プラグインのバージョンにも依存するのかもしれません ライセンスキーを投入しない限り、60 日の評価版扱いになります 見た感じ、vROps 本来のインストールを行った場合の、高速インストール に相当 する仕上がりになりそうです。ノード名など細かい指定ができません できあがるのは、マスターノードがひとつだけ のクラスタです。マルチノードにするには、このあとで追加で作業（プラグインは使わない）が必要です デプロイメントサイズにラボ環境でよく使う 極小 が 選べません。小、中、大、特大、の四択でした。極小をデプロイする場合は、 手順 最初からハマりどころですが、おそらくバグで、日本語 UI だと失敗します。まずは vSphere Client の環境設定で、UI を英語に変更します。この挙動については、後述の トラブルシュート で紹介しています。\n英語 UI にできたら、メニュから [vRealize Operations] を開きます。vROps の導入後はここが簡単なダッシュボードになるわけですが、構築前の段階ではインストールや既存インスタンスの登録のための画面です。\n[INSTALL] を押下すると、ウィザードが起動します。vROps Manager のアプライアンスイメージ（OVF ファイル）を自動でインタネットからダウンロードさせるにはオンラインモードを、手元の OVA ファイルをアップロードして利用する場合はオフラインモードを選択します。\nあとは、インストール先の vCenter Server の認証情報を入力して、\n配置先の情報や、必要なパラメータを入力していくだけです。先述の通り、デプロイサイズに 極小は選べません。\n現在操作中の vCSA をそのまま今回デプロイする vROps の監視対象とする場合は、ここでチェックを入れると、必要な登録作業も自動で行ってくれます。\nあとは [INSTALL] を押下するだけです。\n手元の環境で、だいたい 30 分くらいで完成しました。\nこの画面では、右上の [Quick Links] からいくつかのページを切り替えられますが、概要レベルのダッシュボードしか閲覧できません。\nより細かい確認や操作をしたい場合は、通常の vROps と同様、直接さわる必要があります。vROps のインスタンスの画面は、画面上部か [Quick Links] 内のリンクから開けます。\n初期設定 この方法でデプロイした場合、ユーザ名は admin、パスワードは Vmware@123 でログインできます。\n初回ログイン後は、インスタンスの初期設定画面が開きます。EULA への同意や、ライセンスキーの投入、CEIP の構成などを行います。\nライセンスキーは、設定しない場合は評価版になります。デプロイしてから 60 日です。\n最後の画面で [完了] すると、あとは普通の vROps です。\n先述したとおり、高速インストール 相当の簡易設定しかされていないので、\nパスワードの変更 NTP の設定 あたりは少なくとも実施したほうがよいでしょう。\nほか、ui/ でなく admin/ にアクセスすると、クラスタの管理なども行えます。\nトラブルシュート： 日本語 UI だとデプロイできない 最初、日本語の UI のままで操作をしていたのですが、[インストール] を押下したあと、のほほんと待っていたら、失敗しました。こんなメッセージです。\nステータス: vROps のインストールに失敗しました… 現在の状態: OVF のデプロイが失敗しました\n少し調べると、\nインベントリに仮想マシンができていない タスクの履歴を見ても、OVF テンプレートのデプロイが行われた形跡が一切ない な感じで、そもそもデプロイが走っていませんでした。複数回試行しても状況は変わらず。\nとりあえずログを見てみます。vCSA の /storage/log/vmware/vsphere-ui/logs/vsphere_client_virgo.log です。この中の、\ncom.vmware.vropspluginui.* com.vmware.unicorn.installer.InstallVrops com.vmware.vrealize.lcm.* あたりを追いかけると、\n[INFO ] http-nio-5090-exec-135 com.vmware.vropspluginui.mvc.ServicesController Installation type : online [INFO ] http-nio-5090-exec-135 com.vmware.unicorn.installer.InstallVrops Execute Method [INFO ] http-nio-5090-exec-135 com.vmware.unicorn.installer.InstallVrops [initiateDeployVROPS] - Initiating deploy VROPS Instance [INFO ] http-nio-5090-exec-135 com.vmware.unicorn.installer.InstallVrops buildurlhttps://********/vRealize-Operations-Manager-Appliance-8.0.0.14857692_OVF10.ova ... [INFO ] http-nio-5090-exec-135 c.v.vrealize.lcm.drivers.vsphere65.deploy.impl.BaseOvfDeploy OVA Extraction folder is :/tmp/conf/ovf//vRealize-Operations-Manager-Appliance-8.0.0.14857692_OVF10 [INFO ] http-nio-5090-exec-135 c.v.vrealize.lcm.drivers.vsphere65.vlsi.utils.OVAExtractHelper Starting to extract the OVA from https://********/vRealize-Operations-Manager-Appliance-8.0.0.14857692_OVF10.ova [INFO ] http-nio-5090-exec-135 c.v.vrealize.lcm.drivers.vsphere65.deploy.impl.BaseOvfDeploy OVF file after extraction: /tmp/conf/ovf/vRealize-Operations-Manager-Appliance-8.0.0.14857692_OVF10/vRealize-Operations-Manager-Appliance-8.0.0.14857692_OVF10.ovf ... [INFO ] http-nio-5090-exec-135 c.v.vrealize.lcm.drivers.vsphere65.deploy.impl.BaseOvfDeploy Cluster provided is: kuro-hc01 [INFO ] http-nio-5090-exec-135 c.v.vrealize.lcm.drivers.vsphere65.deploy.impl.BaseOvfDeploy VC will select the host itself. resourcePool if provided will be used [INFO ] http-nio-5090-exec-135 com.vmware.vrealize.lcm.drivers.vsphere65.vlsi.utils.CoreUtility ResourcePool not provided. Using the defaultresgroup-8 [INFO ] http-nio-5090-exec-135 com.vmware.vrealize.lcm.drivers.vsphere65.vlsi.utils.CoreUtility Datastore found for DC and cluster: local-ds02 [INFO ] http-nio-5090-exec-135 c.v.vrealize.lcm.drivers.vsphere65.deploy.impl.BaseOvfDeploy Getting OvfManager from VC ServiceContent [INFO ] http-nio-5090-exec-135 c.v.vrealize.lcm.drivers.vsphere65.deploy.impl.BaseOvfDeploy Getting VM folder from Datastore [INFO ] http-nio-5090-exec-135 c.v.vrealize.lcm.drivers.vsphere65.deploy.impl.BaseOvfDeploy Returning VMfoldergroup-v3 [INFO ] http-nio-5090-exec-135 c.v.vrealize.lcm.drivers.vsphere65.deploy.impl.BaseOvfDeploy Creating Import result [INFO ] http-nio-5090-exec-135 c.v.vrealize.lcm.drivers.vsphere65.deploy.impl.BaseOvfDeploy creating import params [ERROR] http-nio-5090-exec-135 com.vmware.unicorn.installer.InstallVrops [initiateDeployVROPS] - Failed to install vrops : A specified parameter was not correct: cisp.deploymentOption [INFO ] http-nio-5090-exec-135 com.vmware.vropspluginui.mvc.ServicesController installvrops Controller End ... な感じ（一部マスクしています）で、19 行目の通り、OVF のデプロイに渡すパラメータのうちのなにかがダメで死んでいそうです。\nFailed to install vrops : A specified parameter was not correct: cisp.deploymentOption\nリリースノートの Known Issue にも近い事象はなく、以下の類似の KB には vSphere 7.0 では試しようのない解決策しか書いてありません。\nOVA Deployment of vRealize Operations Manager fails in vSphere Client (HTML5) (KB5328) こういう、よくわからず困ったときは、ググりやすいようにメッセージを英語にするため、UI を英語 にするとよいものです。\nで、英語にできたので、英語のエラーメッセージを確認すべく再試行したわけですが、そうしたら 再現しませんでした。若干信じがたかったので、いちど vROps を消して再試行しましたが、やはり日本語だとダメで、英語だとうまくいくように見えました。\nとなると、言語環境の問題とアタリがつけられます。ウィザードで入力したパラメータ群は、デプロイをキックする別の API に何らかの形で渡されていることが想像できるので、API リクエストのペイロードを追ってみます。怪しいのは、日本語の UI で [インストール] を押下したときに送られている中身ですね。\nすると、輝く日本語。\ndeploymentConfig: **==\u0026quot;小\u0026quot;==**\nなるほど、これはもう完全に怪しいですね。英語で操作するとここは \u0026quot;Small\u0026quot; になるようです。\nもう一歩切り分けるため、\u0026quot;小\u0026quot; を \u0026quot;Small\u0026quot; にだけ変えて、このセッションの API リクエストを、ヘッダもクッキーも全部まるっとリプレイしてみます（見せるべきものでもないのでいろいろ隠しています。\n$ curl -s \u0026#39;https://kuro-vcs01.krkb.lab/ui/vropspluginui/rest/services/vropsinstallation\u0026#39; \\ \u0026gt; ... \u0026gt; -H \u0026#39;referer: https://kuro-vcs01.krkb.lab/ui/vropspluginui/resources/index.html?locale=ja_JP\u0026#39; \\ \u0026gt; -H \u0026#39;accept-language: ja-JP,ja;q=0.9,en;q=0.8\u0026#39; \\ \u0026gt; -H \u0026#39;cookie: VSPHERE-USERNAME=Administrator%40VSPHERE.LOCAL; VSPHERE-CLIENT-SESSION-INDEX=********; VSPHERE-UI-JSESSIONID=********; VSPHERE-UI-XSRF-TOKEN=********; ...\u0026#39; \u0026gt; --data-binary \u0026#39;{\u0026#34;vcDetails\u0026#34;:{...},\u0026#34;hostDetails\u0026#34;:{\u0026#34;datacenter\u0026#34;:\u0026#34;kuro-dc01\u0026#34;,\u0026#34;cluster\u0026#34;:\u0026#34;kuro-hc01\u0026#34;,\u0026#34;host\u0026#34;:\u0026#34;kuro-esxi02.krkb.lab\u0026#34;,\u0026#34;datastore\u0026#34;:\u0026#34;local-ds02\u0026#34;,\u0026#34;network\u0026#34;:\u0026#34;vlan01-ext192\u0026#34;,\u0026#34;deploymentConfig\u0026#34;:\u0026#34;Small\u0026#34;},\u0026#34;networkDetails\u0026#34;:{...},\u0026#34;installationmode\u0026#34;:\u0026#34;online\u0026#34;,\u0026#34;vmName\u0026#34;:\u0026#34;kuro-vro01\u0026#34;,\u0026#34;vcAdapterConf\u0026#34;:{...}}\u0026#39; \\ \u0026gt; --compressed \\ \u0026gt; --insecure The Deployment Process Started そのまましばらく待つと、無事に完成しました。\nということで、どうやらローカライズ関連のバグのようなものだと言えそうです。評価用ライセンスでサポートケースもオープンできないのですが、既知か未知かよくわかりません……。\nまとめ vROps のプラグインをつかって、vSphere 環境に vROps をデプロイし、設定しました。\nすごく簡単…… のはずだったのに言語の壁があったのは予想外でしたが、それはそれで、トラブルシュートはよいお勉強になりますし、実装を掘っていくのはたのしいですし、総じてよい機会でした。\nAWS や Azure の情報も vROps に食べさせられるみたいなので、この辺もそのうちさわってみましょう。\n追記 後日追加で確認したところ、本エントリで記載した 日本語 UI ではインストールが失敗する問題 は、エントリ中の 7.0.0.10400（7.0.0b）だけでなく 7.0.0.10600（7.0.0c）でも再現しました。\nとても軽微だし既知かもしれないとは思いつつ、報告はするだけしたほうが良い気もしたので、VMUG の方々に方法を相談したところ、Feature Request がよいのではとの案をいただきました。というわけで送信済みです。\n","date":"2020-08-16T18:44:54Z","image":"/archives/3346/img/image-158.png","permalink":"/archives/3346/","title":"vSphere 7.0 の環境に vROps をさくっと導入する"},{"content":"はじめに 先日のエントリ で、vCenter Server Appliance を 6.7 から 7.0 にアップグレードし、エントリの最後では vCSA 自身を自分専用の DNS サーバにする方法を紹介 しました。\nが、前述の方法では外部からの DNS クエリは受け付けられず、vCSA 以外からは DNS サーバとしては使えません。とはいえ、ラボ内にいろいろ立てようとすると、どうしても自前の DNS サーバがないと不自由します。\nそこで今回は、vCSA 7.0 を外から正引き・逆引きができる簡易内部 DNS サーバとして使うための設定 を紹介します。具体的には、以下を行います。\nDnsmasq を構成して、ループバック IP アドレス以外でも待ち受けるようにする ファイアウォールを構成して、53 番ポートを開放する 当然ながら VMware はサポートしない 方法なので自己責任ではありますが、追加でサーバやコンテナを立てる必要なくすぐに動かせるので、ラボ環境などで使えるシーンもありそうです。\n/etc/hosts の修正 Dnsmasq は、/etc/hosts の中身を名前解決のソースとして参照してくれる機能を持っています。つまり、Dnsmasq を導入したホスト（今回は vCSA）の /etc/hosts に必要なホスト名と IP アドレスの対応付けを全部書いておけば、それを外部から DNS でクエリできるようになるわけです。\nvCSA に SSH でログインし、まずは /etc/hosts に情報を追加します。\n# vi /etc/hosts # cat /etc/hosts ... 192.168.0.200 kuro-qnap01.krkb.lab kuro-qnap01 192.168.0.201 kuro-vcs01.krkb.lab kuro-vcs01 192.168.0.202 kuro-esxi01.krkb.lab kuro-esxi01 192.168.0.203 kuro-esxi02.krkb.lab kuro-esxi02 192.168.0.210 kuro-vro01.krkb.lab kuro-vro01 Dnsmasq の構成 続けて、Dnsmasq の設定ファイルを修正します。行っているのは、次の 3 点です。\nlisten-address=127.0.0.1 をコメントアウトする ループバック IP アドレス以外でも待ち受けさせるため no-hosts をコメントアウトする /etc/hosts をソースとして参照させるため bogus-priv を追加する プライベート IP アドレスの逆引きクエリを上位の DNS サーバにフォワーディングしないようにするため # sed -i \u0026#39;s/^listen-address/#listen-address/\u0026#39; /etc/dnsmasq.conf # sed -i \u0026#39;s/^no-hosts/#no-hosts\\nbogus-priv/\u0026#39; /etc/dnsmasq.conf # cat /etc/dnsmasq.conf #listen-address=127.0.0.1 bind-interfaces user=dnsmasq group=dnsmasq no-negcache #no-hosts bogus-priv log-queries log-facility=/var/log/vmware/dnsmasq.log domain-needed dns-forward-max=150 cache-size=8192 neg-ttl=3600 最後に、Dnsmasq を再起動します。\n# systemctl restart dnsmasq なお、Dnsmasq にとっての上位の DNS サーバは、自ホストの /etc/resolv.conf を参照して判断されます。vCSA の /etc/resolv.conf には vCSA の構築時に指定した DNS サーバ（以下の例では 192.168.0.1）が入ります。この設定は、VAMI で変更できます。\n# cat /etc/resolv.conf ... nameserver 127.0.0.1 nameserver 192.168.0.1 よって、/etc/hosts 内と併せてインタネット上のホスト名も名前解決できるようにしたい場合は、VAMI で上位 DNS サーバを指定するか、もしくは 先ほど編集した /etc/dnsmasq.conf に server=192.168.0.1 など上位 DNS サーバを指定する行を追記するとよいでしょう。\nさて、この段階で、vCSA は 127.0.0.1 だけでなく外部 IP アドレス（この例では 192.168.0.201 でも 53 番ポートを待ち受けるようになります。自ホスト内であれば、すでに以下のように名前解決ができるようになっているはずです。\n# ss -ltun | grep 53 ... udp UNCONN 0 0 127.0.0.1:53 0.0.0.0:* udp UNCONN 0 0 192.168.0.201:53 0.0.0.0:* ... tcp LISTEN 0 32 127.0.0.1:53 0.0.0.0:* tcp LISTEN 0 32 192.168.0.201:53 0.0.0.0:* # dig kuro-vro01 @192.168.0.201 ... ;; ANSWER SECTION: kuro-vro01. 0 IN A 192.168.0.210 ... # dig vmware.com ... ;; ANSWER SECTION: vmware.com. 150 IN A 45.60.11.183 vmware.com. 150 IN A 45.60.101.183 ... ただし、ファイアウォールが動作しているため、外部からはまだ問い合わせができません（以下は Windows から問い合わせた例）。\n\u0026gt; nslookup kuro-vro01 192.168.0.201 DNS request timed out. timeout was 2 seconds. サーバー: UnKnown Address: 192.168.0.201 ... *** UnKnown への要求がタイムアウトしました ファイアウォールの構成 vCSA では、標準でファイアウォールが動作しており、vSphere 的に不要なポートへのアクセスはすべて遮断されています。また、vCSA の VAMI にあるファイアウォールの設定機能では、ポート単位の変更はできず、今回の目的では使えません。\nこのため、GUI や CLI での構成ではなく、vCSA が起動時に読み込む生のファイアウォール用の設定ファイル群に、DNS サーバ用の 53 番ポートを開放する設定を追加して対応します。\nファイアウォール用の設定ファイル群は、/etc/vmware/appliance/firewall ディレクトリ配下に置かれています。ここに JSON 形式で設定を記述したファイルを置くだけです。……今回の目的以外でもいろいろ遊べそうですね。\n今回は DNS サーバなので、TCP と UDP の 53 番ポートを指定します。\n# cat \u0026lt;\u0026lt; EOF \u0026gt; /etc/vmware/appliance/firewall/vmware-dnsmasq { \u0026#34;firewall\u0026#34;: { \u0026#34;enable\u0026#34;: true, \u0026#34;rules\u0026#34;: [ { \u0026#34;direction\u0026#34;: \u0026#34;inbound\u0026#34;, \u0026#34;protocol\u0026#34;: \u0026#34;tcp\u0026#34;, \u0026#34;porttype\u0026#34;: \u0026#34;dst\u0026#34;, \u0026#34;port\u0026#34;: \u0026#34;53\u0026#34;, \u0026#34;portoffset\u0026#34;: 0 }, { \u0026#34;direction\u0026#34;: \u0026#34;inbound\u0026#34;, \u0026#34;protocol\u0026#34;: \u0026#34;udp\u0026#34;, \u0026#34;porttype\u0026#34;: \u0026#34;dst\u0026#34;, \u0026#34;port\u0026#34;: \u0026#34;53\u0026#34;, \u0026#34;portoffset\u0026#34;: 0 } ] } } EOF ファイアウォールの設定を再読み込みさせます。\n# /usr/lib/applmgmt/networking/bin/firewall-reload これで、ファイアウォールの実体である iptables に設定が反映されます。実際に、TCP と UDP の 53 番ポートのルールが追加されている様子が確認できます。\n# iptables -n --list | grep 53 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:53 ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:53 動作確認 これで、Windows など外部からも聴いたら答えてくれる DNS サーバができました。\n\u0026gt; nslookup kuro-vro01 192.168.0.201 サーバー: kuro-vcs01.krkb.lab Address: 192.168.0.201 名前: kuro-vro01 Address: 192.168.0.210 UDP でなく、TCP で問い合わせても返してくれます。\n$ dig kuro-vro01 +tcp @192.168.0.201 ... ;; ANSWER SECTION: kuro-vro01. 0 IN A 192.168.0.210 ... 逆引きもできます。\n$ dig -x 192.168.0.210 @192.168.0.201 ... ;; ANSWER SECTION: 210.0.168.192.in-addr.arpa. 0 IN PTR kuro-vro01.krkb.lab. ... Dnsmasq の上位 DNS サーバが正しく構成できていれば、インタネット上のホスト名も解決できます。\n$ dig vmware.com @192.168.0.201 ... ;; ANSWER SECTION: vmware.com. 150 IN A 45.60.101.183 vmware.com. 150 IN A 45.60.11.183 .... まとめ vCSA 7.0 を、正引き・逆引きができる程度の簡単な内部 DNS サーバとして無理やり使う方法を紹介しました。\n冒頭で書いた通り、VMware がサポートする方法ではもちろんありませんが、追加でサーバやコンテナを立てる必要なくすぐに動かせるので、自宅ラボなどでは気軽で便利な解決策になるのではないでしょうか。\n","date":"2020-08-14T15:29:23Z","image":"/archives/3259/img/2020-08-15-001903.png","permalink":"/archives/3259/","title":"vCSA 7.0 をラボ環境用の簡易内部 DNS サーバとして利用する"},{"content":"追記： PowerCLI 12.1 で正式サポート PowerCLI 12.1 がリリースされました。このバージョンから、New-ContentLibraryItem コマンドレットに引数 -Uri が追加され、ds:// スキーマによるデータストアからの直接のアップロードが正式にサポートされています。\nNew-ContentLibraryItem - Cmdlet Reference - VMware {code} PowerCLI Change Log The Uri, FileName, and SslThumbprint parameters have been added to the New-ContentLibraryItem cmdlet to allow uploading files from the Internet or datastore URLs. PowerCLI Change Log\r1 ファイル 1 行でアップロードできます。\nPS\u0026gt; New-ContentLibraryItem -ContentLibrary ISO -Name rhel-8.2-x86_64-dvd.iso -FileName rhel-8.2-x86_64-dvd.iso -Uri ((Get-Datastore local-ds02).ExtensionData.Info.Url + \u0026#34;iso/rhel-8.2-x86_64-dvd.iso\u0026#34;) Name ContentLibrary ---- -------------- rhel-8.2-x86_64-dvd.iso ISO 簡単ですね！\n以下、元のエントリの内容は要らない子になってしまいましたが、残しておきます。\nやりたかったこと つまり、\nデータストアに直接保存しているたくさんの ISO ファイル を どうにかして コンテンツライブラリに簡単にインポートしたい のです。\nでも、コンテンツライブラリは、\nローカルファイルのアップロード HTTP でのどこかからのダウンロード のどちらかでしかインポートできないようで、愚直にやろうとすると、いちどローカルに全部ダウンロードして、それを再度アップロード することになります。\nが、もちろん、これは数が多いと相当つらいので、強引でもいいからどうにかしてラクをできないかなあ、という話です。\n解決策： データストアから HTTP でダウンロードさせる PowerCLI スクリプトでの実装 簡単な PowerCLI の関数、Import-ContentLibraryItemFromDatastore を作りました。\nImport ISO files to Content Library from Datastore こんな感じで、データストア上のファイルのパスと認証情報を渡すと、どうにかしてくれます。\n\u0026gt; Import-ContentLibraryItemFromDatastore -Username temporary-user-01@vsphere.local -Password my-password -Item vmstore:\\sandbox-dc01\\sandbox-ds01\\ISO -DestinationLibraryName \u0026#34;ISO Images\u0026#34; Creating Sample_ISO_File.iso ... Creating Update Session for Sample_ISO_File.iso ... Pulling File Sample_ISO_File.iso from Datastore ... Waiting for Transfer ... Transferring ... 29570480 of 367654912 bytes Transferring ... 91598704 of 367654912 bytes Transferring ... 154513184 of 367654912 bytes Transferring ... 215776424 of 367654912 bytes Transferring ... 271944344 of 367654912 bytes Transferring ... 322272864 of 367654912 bytes Completed. Completing the Session ... Waiting for Complete ... Completed. Deleting the Session ... トラフィックは、データストアを参照できる ESXi からいちど vCenter Server を経由して、再度 ESXi に流れてコンテンツライブラリのバックエンドのデータストアに書き込まれます。オーバヘッドはそれなりにあるものの、ローカルを経由させるよりは圧倒的に効率的…… なはずです。\n-Item では、PowerCLI で接続すると利用できる vmstore:\\ 表記で目的のファイルのパスを指定します。ファイルを指定した場合はそのファイル、ディレクトリを指定した場合は配下のファイルすべて、さらに -Recurse スイッチを追加した場合は再帰で取り込み対象が検索されます。ただし、現在では対象になるのは ISO ファイルのみ です。\n後述しますが、-Username と -Password は、内部でプレーンテキストで利用 されます。この影響で、各種ログなどにプレーンテキストで認証情報が残る 可能性があり、よって、利用する場合は 一時的な専用のユーザを作る ほうがよいでしょう。\n詳しい実装は GitHub のリポジトリ を参照してください。\nインポート後も、元の ISO ファイルは元のデータストア上に残るので、必要に応じて掃除をします。\nPowerCLI スクリプトの利用例 例えば、次の 2 つの ISO ファイルをインポートしたいとします。\nPS \u0026gt; Get-ChildItem vmstore:\\kuro-dc01\\local-ds01\\ISO | Select Name, ItemType, Length Name ItemType Length ---- -------- ------ Sample_ISO_File_1.iso IsoImageFile 367654912 Sample_ISO_File_2.iso IsoImageFile 88188928 作業前なので、コンテンツライブラリは空っぽです。\nPS \u0026gt; Get-ContentLibraryItem -ContentLibrary \u0026#34;ISO Images\u0026#34; PS \u0026gt; 作った関数でインポートさせます。事前に Connect-VIServer と Connect-CisServer（どちらも接続先は vCenter Server）はしておきます。\nPS \u0026gt; Import-ContentLibraryItemFromDatastore -Username username@vsphere.local -Password my-secure-password -Item vmstore:\\kuro-dc01\\local-ds01\\ISO -DestinationLibraryName \u0026#34;ISO Images\u0026#34; Creating Sample_ISO_File_1.iso ... Creating Update Session for Sample_ISO_File_1.iso ... Pulling File Sample_ISO_File_1.iso from Datastore ... Waiting for Transfer ... Waiting for Transfer ... Transferring ... 32245264 of 367654912 bytes Transferring ... 92454328 of 367654912 bytes Transferring ... 154309064 of 367654912 bytes Transferring ... 210416392 of 367654912 bytes Transferring ... 280139640 of 367654912 bytes Transferring ... 336237712 of 367654912 bytes Completed. Completing the Session ... Waiting for Complete ... Completed. Deleting the Session ... Creating Sample_ISO_File_2.iso ... Creating Update Session for Sample_ISO_File_2.iso ... Pulling File Sample_ISO_File_2.iso from Datastore ... Waiting for Transfer ... Transferring ... 26515392 of 88188928 bytes Transferring ... 88188928 of 88188928 bytes Completed. Completing the Session ... Waiting for Complete ... Completed. Deleting the Session ... インポートされました。\nPS \u0026gt; Get-ContentLibraryItem -ContentLibrary \u0026#34;ISO Images\u0026#34; Name ContentLibrary ItemType SizeGB ---- -------------- -------- ------ Sample_ISO_File_1 ISO Images iso 0.342 Sample_ISO_File_2 ISO Images iso 0.082 仮想マシンからもマウントできます。\n確認できたら、元のファイルを消して完了です。\nPS \u0026gt; Remove-Item vmstore:\\kuro-dc01\\local-ds01\\ISO -Recurse 仕組みと手作業で行うときの手順 コンテンツライブラリは、HTTP で公開されているコンテンツならインポートさせられるので、つまり、データストアの中身に vCenter Server が HTTP でアクセスできればよい わけです。\nそして、vCenter Server も ESXi も、実は vSphere Web Service API により、ブラウザでデータストアにアクセスできます。vSphere Client の、ログイン画面の手前の右上のアレです。\nつまり、データストアの任意のファイルに HTTP でアクセスできる ということです。\n実際に掘っていき、目的の ISO ファイルを確認します。\nこのファイルの URL を確認すると、次のような URL が得られます。これが、ISO ファイルのダウンロード用の URL です。URL の書式の詳細はガイドに記載 があります。\nhttps://kuro-vcs01.krkb.lab/folder/ISO/Sample_ISO_File_1.iso?dcPath=kuro-dc01\u0026amp;dsName=local-ds01 ただし、この URL へのアクセスには認証が必要です。実際、例えば vCSA から wget でアクセスしようとすると、401 が返ります。\nroot@record [ ~ ]# wget \u0026#39;https://kuro-vcs01.krkb.lab/folder/ISO/Sample_ISO_File_1.iso?dcPath=kuro-dc01\u0026amp;dsName=local-ds01\u0026#39; -O Sample_ISO_File_1.iso --2020-08-09 00:29:57-- https://kuro-vcs01.krkb.lab/folder/ISO/Sample_ISO_File_1.iso?dcPath=kuro-dc01\u0026amp;dsName=local-ds01 Resolving kuro-vcs01.krkb.lab... 192.168.0.201 Connecting to kuro-vcs01.krkb.lab|192.168.0.201|:443... connected. HTTP request sent, awaiting response... 401 Unauthorized Username/Password Authentication Failed. が、BASIC 認証なので、URL にユーザ名とパスワードを含めて しまえば、実はアクセスできます。\nURL に BASIC 認証の情報を含めるには、\nhttps://\u0026lt;ユーザ名\u0026gt;:\u0026lt;パスワード\u0026gt;@example.com/ とするだけです。ただし、ユーザ名の @ は %40 にエンコードします。よって、今回の URL であれば、\nhttps://username%40vsphere.local:my-secure-password@kuro-vcs01.krkb.lab/folder/ISO/Sample_ISO_File_1.iso?dcPath=kuro-dc01\u0026amp;dsName=local-ds01 とすればアクセスできます。\nroot@record [ ~ ]# wget \u0026#39;https://username%40vsphere.local:my-secure-password@kuro-vcs01.krkb.lab/folder/ISO/Sample_ISO_File_1.iso?dcPath=kuro-dc01\u0026amp;dsName=local-ds01\u0026#39; -O Sample_ISO_File_1.iso --2020-08-09 00:30:21-- https://username%40vsphere.local:*password*@kuro-vcs01.krkb.lab/folder/ISO/Sample_ISO_File_1.iso?dcPath=kuro-dc01\u0026amp;dsName=local-ds01 Resolving kuro-vcs01.krkb.lab... 192.168.0.201 Connecting to kuro-vcs01.krkb.lab|192.168.0.201|:443... connected. HTTP request sent, awaiting response... 401 Unauthorized Authentication selected: Basic realm=\u0026#34;VMware HTTP server\u0026#34; Reusing existing connection to kuro-vcs01.krkb.lab:443. HTTP request sent, awaiting response... 200 OK Length: 367654912 (351M) [application/octet-stream] Saving to: ‘Sample_ISO_File_1.iso’ Sample_ISO_File_1.iso 100%[=================================================================================================\u0026gt;] 350.62M 69.8MB/s in 4.8s 2020-08-09 00:30:26 (72.8 MB/s) - ‘Sample_ISO_File_1.iso’ saved [367654912/367654912] よって、コンテンツライブラリのインポートソースとして、このような認証情報を含めた URL を指定することで、vCenter Server が直接 vCenter Server（や ESXi）からデータストアのコンテンツに HTTP でアクセスでき、結果として、ほぼ直接のインポートが成功します。\n先述のスクリプトは、このような URL の組み立てや、HTTP ソースからのインポートを自動で行わせているだけです。\n試したけどダメだった例 PowerCLI で vmstore:// 指定 PowerCLI でもコンテンツライブラリはいじれるようになっていて、組み込みのコマンドレットの New-ContentLibraryItem でコンテンツライブラリにファイルを追加できます。\nNew-ContentLibraryItem ちまちまとコーディングをせずとも、これが使えるのがどう考えてもいちばん楽なので、安直な発想で Files の指定をこねくり回しました。\nが、\nFiles に vmstore:\\ のパスを直接書く New-ContentLibraryItem : パラメーター 'Files' の引数変換を処理できません。指定されたパスのフォーマットはサポートされていません。 のエラー New-PSDrive でデータストアにドライブ文字（V:\\ など）を割り当てて、Files には V:\\ のパスを書く New-PSDrive -Name \u0026quot;V\u0026quot; -PSProvider VimDatastore -Root \u0026quot;\\\u0026quot; -Location (Get-Datastore local-ds01) など New-ContentLibraryItem : パラメーター 'Files' の引数変換を処理できません。Supplied path ('V:\\ISO\\Sample_ISO_File_1.iso') must exist. のエラー で、いずれもダメでした。\nPowerCLI で https:// や ds:// 指定 続けて、認証情報を埋め込んだ URL や ds:// を New-ContentLibraryItem に渡してみましたが、いずれもダメでした。\nFiles に認証情報を含んだ URL を指定 Unable to transform value 'https://...' into path. のエラー Files に ds:// で始まるパスを指定 指定されたパスのフォーマットはサポートされていません。 のエラー vSphere Automation API で ds:// 指定 ネイティブのコマンドレットが使えないことが分かったので、Get-CisService で Automation API を呼び出して使う作戦に変更しました。\nコンテンツライブラリへのインポートは、Automation API では content.item.updatesession.file の add を使いますが、このときにリクエストに含める FileAddSpec を見ると、source_endpoint（ItemTransferEndpoint）の uri には、データストア内のコンテンツを示す ds:// スキーマが使えるように見えます。\ncontent.item.updatesession.file.add FileAddSpec ItemTransferEndpoint Transfer endpoint URI. The supported URI schemes are: http, https, and ds. An endpoint URI with the ds scheme specifies the location of the file on the datastore. The format of the datastore URI is: ds:///vmfs/volumes/uuid/path When the transfer endpoint is a datastore location, the server can import the file directly from the storage backing without the overhead of streaming over HTTP. ItemTransferEndpoint\rURL にパスワードを埋め込むなどというお行儀の悪い方法より、これが使えるならこの方が良いに決まっているので、最初はこれを使うつもりでした。\nds:// 表記で組んでみると、エラーもなく動くし、転送タスクも流れるし、一見インポートできたかのようにはなりました。\nPS \u0026gt; Import-ContentLibraryItemFromDatastore -Username username@vsphere.local -Password my-secure-password -Item vmstore:\\kuro-dc01\\local-ds01\\ISO -DestinationLibraryName \u0026#34;ISO Images\u0026#34; Creating Sample_ISO_File_1.iso ... Creating Update Session for Sample_ISO_File_1.iso ... Pulling File Sample_ISO_File_1.iso from Datastore ... Waiting for Transfer ... Transferring ... 0 of 73728 bytes Transferring ... 7372 of 73728 bytes Transferring ... 7372 of 73728 bytes Transferring ... 7372 of 73728 bytes Transferring ... 7372 of 73728 bytes Transferring ... 7372 of 73728 bytes Transferring ... 7372 of 73728 bytes Transferring ... 7372 of 73728 bytes Completed. Completing the Session ... Waiting for Complete ... Completed. Deleting the Session ... が、\nAPI が返すファイルサイズが明らかにおかしい（上の例では、300 MB くらいのファイルなのに 70 KB 程度に見える） インポートされたコンテンツライブラリ上のファイルが 4 KB に見える な状態が抜け出せず、size プロパティを指定しても打破できず、ファイル自体もまともに使えないものにしかならなかったので、ダメでした。\nおわりに いろいろ試して最終的にはどうにかしたものの、そもそも、データストアからの直接のインポートは、もはや普通にできてしかるべきなのではという気がすごくしています。\n実際、フォーラムを見ると、ちらほらと『できないの？』な質問はあるようです。\nUpload ISOs to Content Library from a datastore. Content Library in version 6.5 working with ISO files already present on a datastore Is it possible to Move Datastore ISO files to content library ? いずれも解決策は書かれていないので、しかたなく今回のような（あまりきれいではない）実装に至ったわけですが、公式の機能追加を待ちたいですね。\nとはいえ、ホームユース程度であればこれで充分で、もともとやりたかった目の前の課題（大量のデータストア上の ISO をコンテンツライブラリに突っ込む）にはひとまず対応できそうです。\n","date":"2020-08-08T17:33:28Z","image":"/archives/3235/img/ss.png","permalink":"/archives/3235/","title":"vCenter Server のコンテンツライブラリにデータストアから直接 ISO ファイルをインポートする"},{"content":"はじめに VMware Cloud Foundation（VCF）という、SDDC Manager で SDDC 環境全体をイイ感じに管理できる製品があり、これを（ハンズオンラボでなく）自宅で触りたくなりました。ただの趣味というか興味です。\nVMware Cloud Foundation しかし VCF は、お行儀のよい SDDC 環境をしっかりした規模で作るための製品なので、自前で構築しようと思えば、たとえ最小構成でも、256 GB のメモリと 10 TB 超の SSD と 2 つの 10 GbE の NIC を積んだサーバを 4 台用意しないといけません。また、上位ネットワークとは BGP で会話できないといけないですし、DNS サーバや DHCP サーバなど周辺にもいろいろ必要です。さらにはネットワーク構成の詳細やさまざまなオブジェクトの命名など ワークブック の 400 ちかいパラメータを決定する必要もあります。\n要するに、自宅で気軽に触れる程度のものでは到底ないわけです。ネステッドで作ればハードウェア面はある程度ごまかせはするものの、周辺サーバの用意やパラメータの決定は、ちょっと触れればいいとほんのり思っているくらいのモチベーションではしんどいものがあります。\nとはいえ、海外のさかんな自宅ラボ事情を考えると、自宅で触りたい勢は世界のどこかには存在しているはずで、そう思っていろいろ調べていると、VCF Lab Constructor（VLC）なるものの存在を知りました。このエントリでは、これを使ってネステッドな VCF を構築するまでの流れを紹介します。\nVCF Lab Constructor（VLC） 概要 VCF Lab Constructor（VLC）は、VCF をネステッド環境で簡単に構築できるようにいろいろお膳立てをしてくれるツールです。詳しくは VMware のブログで紹介されています。\nDeep dive into VMware Cloud Foundation – Part 1 Building a Nested Lab Deep dive into VMware Cloud Foundation – Part 2 Nested Lab deployment 具体的には、VLC 経由でデプロイすることで、\n値が全部埋まったサンプルのパラメータ（JSON）が提供されていて、ただ動けばよいレベルであればそのまま使える Cloud Builder アプライアンスをパラメータに従って自動でデプロイしてくれる デプロイした Cloud Builder アプライアンスの中に、DNS サーバや BGP ルータなど、必須の周辺サーバの機能を無理やり同居させてくれる パラメータに従って vSAN の構成要件を満たした ESXi を ESXi 上の仮想マシンとしてデプロイしてくれる （指定すれば）Cloud Builder での Bring-Up プロセスも開始してくれる などのメリットが得られます。これによって、VCF に必要な Cloud Builder と ESXi 環境がサクッと整うため、気軽に VCF を試す環境を得るためのハードルが一気に下がるわけです。\nもちろん公式にサポートはされていませんが、開発は VMware のなかのひとであり、また Slack のコミュニティもあって、それなりにワイワイしています。\n準備 VLC のダウンロード URL は、次の URL から登録することで得られます。\nhttp://tiny.cc/getVLC ここで得られる URL は最新版のダウンロード用ですが、過去のバージョン用の URL は Slack でピン止めされたメッセージでリストされています。\nhttps://tiny.cc/getVLCSlack なお、VCF のバイナリ（Cloud Builder の OVA イメージ）と、VCF を構成する各製品（vCenter Server、ESXi、vSAN、NSX-T）のライセンスキーは別途必要です。ぼくは VMUG Advantage なヒトなのでそれで入手しています。\nハードウェア要件 VLC にはインストールガイドも付属しており、それによると、現在のバージョンでは、\n1 台の ESXi（6.7 以降） 128 GB 以上のメモリ 800 GB 以上の SSD が最低要件とされています。本来の VCF のハードウェア要件からは圧倒的に小さいですが、それでもそこそこのモノは必要ですね。\n今回の環境 といいつつ、残念ながら上記の要件を満たせるハードウェアが自宅にないので、以下の環境で踏ん張ることにしました。\n1 台の vCenter Server で管理される 2 台の ESXi 7.0 各 32 GB のメモリ（2 台の合計で 64 GB） 各 512 GB の SSD（2 台の合計で 1 TB） 共有ストレージなし どうにかして処理を 2 台で分散させることにしてディスク容量を確保し、メモリの不足については、パフォーマンスを犠牲にしてオーバコミットをさせまくる前提にします。\n手順 細かい手順や注意事項は付属のガイドや Slack のピン止めされたメッセージで案内されているので、ここでは概略だけ紹介します。\n環境の準備 VLC に付属のガイドに従って、環境を整えます。\n母艦となる物理 ESXi 側では、vDS で MTU を 9,000 にして、トランクモードのポートグループをひとつ作りました。このポートグループに仮想 ESXi と Cloud Buider が接続され、VCF 関連のすべてのパケットが（VLAN のタグ付きで）流れることになります。ポートグループのセキュリティ設定は、プロミスキャスモードなどもすべて承諾しています。\nまた、今回は二台の ESXi で無理やり分散させるため、vDS のアップリンクもトランクにして、上位にちょっといいスイッチをつなぐことで、ESXi 間で 9,000 バイトのジャンボフレームも VLAN もそのまま流せるようにしています。\n作業端末となる Windows 10（ドキュメントや図で触れられている Jump Host）は、仮想マシンではなく、普段使いの Windows 10 をそのまま使うことにしました。積んでいる Intel の物理 NIC が VLAN を喋れたので、サブインタフェイスを作って VLAN ID や IP アドレスをガイドと同等に構成しています。\nパラメータの用意 VCF の最新は 4.0.1 ですが、VMUG Advantage でダウンロードできる VCF が 4.0 だったので、VLC も 4.0 をダウンロードしました。\nバンドルされている JSON は二つありますが、今回は NO_AVN を使います。AVN（Application Virtual Network）については こちらのエントリで説明 されています。\nAUTOMATED_AVN_VCF_VLAN_10-13_NOLIC_v4.json AUTOMATED_NO_AVN_VCF_VLAN_10-13_NOLIC_v4.json この JSON ファイルは、VLC が Cloud Builder や仮想 ESXi をデプロイするときのパラメータになるだけでなく、Cloud Builder がマネジメントドメインをデプロイするときの Bring-Up プロセスのパラメータとしてもそのまま使えます。\nこの段階で、JSON ファイル中の以下の箇所に、ライセンスキーを埋め込んで上書きしておきます。\n{ ... \u0026#34;esxLicense\u0026#34;: \u0026#34;\u0026lt;Insert ESXi License\u0026gt;\u0026#34;, ... \u0026#34;vCenterSpecs\u0026#34;: [ { ... \u0026#34;licenseFile\u0026#34;: \u0026#34;\u0026lt;Insert vCenter License\u0026gt;\u0026#34;, ... } ], \u0026#34;vsanSpecs\u0026#34;:[ { ... \u0026#34;licenseFile\u0026#34;: \u0026#34;\u0026lt;Insert vSAN License\u0026gt;\u0026#34;, ... } ], \u0026#34;nsxtSpec\u0026#34;: { ... \u0026#34;nsxtLicense\u0026#34;: \u0026#34;\u0026lt;Insert NSX License\u0026gt;\u0026#34;, ... }, ... } VLC の起動と設定 VLCGui.ps1 を PowerShell で実行すると、進捗表示とログ表示用の PowerShell ウィンドウと、操作用の GUI が起動します。\nDNS サーバなど、必要な周辺サービス含めて VLC でデプロイする場合は Automated を、自前で用意しているものを使わせる場合は Manual を選択します。今回は全部オマカセしてしまうので、Automated です。\nVCF EMS JSON に先に用意した JSON ファイルを、CB OVA Location に Cloud Builder の OVA ファイルをそれぞれ指定します。右側にはデプロイ先の vCenter Server か ESXi の認証情報を入力して、Connect を押下し、目的の構成を選択状態にします。\nBring-Up プロセスまで続けて実行させてしまう場合は Do Bringup? にチェックを、Cloud Builder と仮想 ESXi のデプロイまでで終える（その先は本来の VCF の構築手順に従って自分でやる）場合はチェックを外します。今回は、小細工もしたいのでチェックを外します。Bring-Up プロセスの詳細を知りたいなどお勉強目的の場合も、チェックを外した方があとあとわかりやすいでしょう。\nこの後、Validate を押下するとバリデーションが走り、問題なければ Construct! が押下できるようになります。\nこのバリデーションで、データストアの空き容量もチェックされます。選択したデータストアに 800 GB の空きがないと怒られてしまうので、今回は VLCGui.ps1 のバリデーションの条件文をちょろまかして強引に進めました。\nConstruct! を押下して走り出してしまえば、あとは待つだけです。Do Bringup? にチェックを入れていた場合は、このまま VCF のマネジメントドメインが完成して SDDC Manager が使える状態になるまで、ひたすら放置すれば勝手に進みます。典型的には 3.5 時間とされていますが、遅い環境だと 9 時間程度かかる例もあるようです（数字だけ見ると長いですが、ほんとうに放置するだけなので、手作業で作るよりは比較にならないほど楽ですね……）。\nDo Bringup? にチェックを入れていない場合は、Cloud Builder のデプロイと仮想 ESXi の構成だけなので、数十分程度で終わるでしょう。\nCloud Builder と仮想 ESXi の完成 本来は Cloud Builder のデプロイだけでも手作業 ですが、VLC では OVF Tools を使ってパラメータを流し込んでいるようです。画面を眺めていると、そうして Cloud Builder がデプロイされて構成されたあと、GoBGP や MaraDNS などを使って Cloud Builder に周辺サービス群がねじこまれていく様子が見えます。\nCloud Builder 関係の作業が終わると、4 台の仮想 ESXi の作成が始まり、そして終わります。ESXi も、本来であれば VIA などを使って手作り する必要があった作業です。ラクですね。\n今回のように Do Bringup? にチェックを入れなかった場合は、VLC の出番はここで終わりです。\nデプロイ先の状態を見ると、Cloud Builder の仮想マシンと、仮想 ESXi が並んでいる様子がわかります。\n今回は、VLC でデプロイ先をローカルデータストアにした影響で、Cloud Builder も仮想 ESXi も物理的に同じ ESXi に載ってしまっています。物理リソースが潤沢であればそれで全く問題はありませんが、今回はそうではないので、この段階で 4 台のうち 2 台の仮想 ESXi をもう一方の物理 ESXi に移行して、コンピュートとストレージの負荷分散をはかりました。\nVCF の Bring-Up プロセスの実行 VCF の初期構築作業の肝（とぼくが勝手に思っている部分）が、この Bring-Up プロセス です。構成さえ事前に決めれば、vCenter Server も vSAN も NSX-T も SDDC Manager も全自動で仕上げてくれる、VCF によるライフサイクル管理のメリットの一端が垣間見える部分です。\n付属の JSON を使った場合は、この段階でホスト名 CB-01a.vcf.sddc.lab で Cloud Builder にアクセスできるようになっているはずです。ログインして、EULA を確認して進めます。\nVxRail ではないのでそのまま次へ。\n環境要件が表示されます。いろいろありますが、事前準備と VLC によって充足されているか、充足できていなくても無視できる程度にはなっているはずです。\n続行するとパラメータを設定するパートに入ります。本来は、ここか VMware の Web サイトからワークブックをダウンロードして、値を埋め、再度ここにアップロードして進めるものですが、VLC の実行時に利用した JSON がここでそのまま使えます。\nつまり、今回は、ワークブックを埋めなくてもよく、JSON をアップロードすれば進めます。\nバリデーションが開始されます。いくつか警告が出ますが、警告であれば確認の上で Acknowledge で了承して進められます。\n続行すると、楽しい構築の始まりです。\nあとは、表示されている大量のタスク（スクロールバー参照）が全自動でごりごり進んでいくのを眺めているだけで終わりです。壮観ですね。\nVLC で Do Bringup? にチェックを入れていた場合は、VLC が Cloud Builder の API 経由でこの Bring-Up プロセスをキックすることになり、その結果、最初から最後まで VLC だけで透過的に全自動、な見え方になります。\nBring-Up プロセスは、早くて数時間、遅いと半日程度はかかると思います。放置してもよいですが、タスクの中身を見たりデプロイ先の各管理画面に直接入って過程を観察したりするとたいへんおもしろく、かつお勉強になり、これを手でやれといわれたら死んでしまう感じです。\nすべてのタスクが Success になって走り切ったら、完成です。\nトラブルシュート Bring-Up プロセスのどこかでエラーが起きたときは、エラーメッセージに加えて、それぞれの管理画面やログファイルを直接のぞくと調べやすくなります。付属の JSON を使った場合は、\nCloud Builder： CB-01a.vcf.sddc.lab ESXi： esxi-[1-4].vcf.sddc.lab vCenter Server： vcenter-mgmt.vcf.sddc.lab NSX-T Manager： nsx-mgmt.vcf.sddc.lab SDDC Manager： sddc-manager.vcf.sddc.lab あたりです。認証情報は JSON に含まれています。\nなお、今回のように物理リソースが貧弱な場合は、たまに単なる高負荷で失敗することもあるようです。ネステッド環境内で DRS による vMotion がキックされることもあり、それが負荷に拍車をかけている場合もあります。\nこのようなとき、単純なリトライで成功する場合もありますが、状況によってはネステッド環境内の DRS のモードを変更したり、リトライ前に手動で vMotion するなどして（物理ホストレイヤでの）負荷の平準化をはかるとよさそうです。VCF 的には DRS はもりもり動かすべきですが、リソースに余裕はないので仕方なしです……。\nこのほか、VLC のガイド内にもヒントがいろいろありますし、Slack にも情報がたまっています。\nVCF の管理と拡張 Bring-Up プロセスが無事に完了したら、SDDC Manager が利用できるようになっているはずです。付属の JSON では、sddc-manager.vcf.sddc.lab です。\nネステッド環境の vCenter Server に入れば、マネジメントドメインの構成も探索できます。\nNSX-T の管理画面も触れますね。\n現時点でできているのはマネジメントドメインだけなので、本来はここからさらにワークロードドメインを追加構築したり、vRealize Suite をデプロイしたり、VCF 4.0 であれば vSphere with Kubernetes を使ったワークロード管理機能を有効化したり、ネットワークをうにゃうにゃしたり、それっぽいことをいろいろやっていくわけです。\nなお、ネステッド環境を拡張したい場合は、VLC の Extension Pack! から、空の ESXi を追加構築できます。追加に必要な JSON も付属しています。\nまとめ VCF Lab Constructor を使って、自宅のラボ環境に、ネステッドな VMware Cloud Foundation 4.0（VCF 4.0）を構築し、その過程を体験・観察しました。\nあたかもサクサク成功させたかのように書いていますが、実際は何度かの失敗のあとにたどりついています。\n単一の ESXi（32 GB のメモリ、512 GB の SSD）のみを使ってデプロイしようとしたら、データストアがあふれて失敗 単一の ESXi（32 GB のメモリ）と外付けの NFS データストア（2 TB の HDD）を使ってデプロイしようとしたら、処理が遅すぎて NSX-T Manager のデプロイから進めず失敗 二台の ESXi（各 32 GB のメモリ）と外付けの共有 NFS データストア（2 TB の HDD）を使ってデプロイしようとしても、NSX-T Manager のデプロイから進めず失敗 要するに、潤沢なリソースはとにかく正義です。富豪的解決はだいじです。動けばいいや、と割り切るにも限度はあり、こういう無茶をしてもまともに動く環境は得られません。\n最終的には本エントリで紹介したように 2 台にうまく分散させることで Bring-Up までは成功しましたが、重すぎてほぼ使い物にならないのが正直な現実でした。\nとはいえ、構築プロセス（の一部）を体験できるという意味で、知的好奇心の充足には非常に有効であり、いろいろ見えておもしろかったです。満足しました。\nもちろん、構築そのものが高度に自動化されているからといって、設計も同じ程度に楽かといえば当然そんなことはまったくないでしょうし、それどころかむしろ設計、とくに周辺サービス群とのインタラクションとパラメータの具体化、そして長期的な運用プロセスの定義こそがキモな気もするわけで、過度な期待はせずに、できるだけ誠実に向き合っていきたいものですね。\n","date":"2020-07-25T16:38:19Z","image":"/archives/3194/img/image-117.png","permalink":"/archives/3194/","title":"VCF Lab Constructor（VLC）でネステッドな VMware Cloud Foundation 4.0 環境を気軽に作る"},{"content":"はじめに 前回のエントリ で、vSphere 6.7 の環境のうち、vCenter Server を 7.0 にアップグレードして、最新のパッチの適用も行いました。このエントリでは、ESXi を 7.0 にアップグレードし、パッチの適用も行います。\nESXi 6.7 から 7.0 へのアップグレード方法は、ドキュメントでも提示されている とおりにいくつかありますが、今回は、vSphere 7.0 からの新機能である、vSphere Lifecycle Manager（vLCM）を利用します。パッチの適用も vLCM を利用します。\n対象の ESXi は、Intel の NUC（NUC8i5BEK）にインストールされています。VMware 的には非サポート（HCL に載っていない）ですが、気にせずできる範囲でやっていきます。\nvSphere Lifecycle Manager これまでも、vSphere 環境のライフサイクル管理の目的で、vSphere Update Manager（VUM）と呼ばれるツールが存在していました。vSphere 7.0 からは、これが vSphere Lifecycle Manager（vLCM）に置き換わっています。\nManaging Host and Cluster Lifecycle VUM も vLCM も、vSphere 環境のバージョンやパッチの適用状態に任意の基準を決めて、実環境のその基準からの逸脱を確認し、基準に準拠させる修正作業も実行できる、というざっくりした説明の限りでは大きな違いはありませんが、その基準の考え方に、従来どおりの『ベースライン』に加えて、『イメージ』という概念が新しく登場しています。この『イメージ』こそが vLCM の大きな特徴なようです。\n考え方 これまでの『ベースライン』による管理では、例えば、ある特定のパッチの適用状態を管理できた一方で、そのパッチ以外のパッケージ構成やバージョンについては、何ら関知しませんでした。つまり、同じベースラインを複数のホストに添付して管理したとしても、その全ホストのパッケージ構成が完全に一致していることは保証できませんでした。\nこれに対し、vLCM で新しく登場した『イメージ』では、\nベースイメージ VMware がバージョンごとにリリースする ESXi そのもので、ブート可能で完全な状態を構成するコンポーネントの集まり ベンダアドオン（任意） OEM ベンダが提供する追加のドライバやパッチ、追加機能など ファームウェアやドライバアドオン（任意） ハードウェアベンダが提供するファームウェアやドライバなど コンポーネント（任意） サードパーティ製の追加ドライバや追加機能など をまとめて管理するようです。つまり、ESXi を構成する全コンポーネントのあるべき状態（Desired State）を（任意でファームウェアまで含めて）宣言的に『イメージ』として定義できます。イマドキな考え方ですね。詳しくはドキュメントに書いてあります。\nvLCM では、従来通りの『ベースライン』による管理と『イメージ』による管理のいずれも利用できますが、\n『イメージ』による管理は、ホストクラスタに対してのみ有効化できる クラスタの全台が ESXi 7.0 以降である必要がある クラスタの全台がステートフル（Auto Deploy でなくディスクからブートする）である必要がある クラスタの全体が同一ベンダのハードウェアである必要がある 『ベースライン』管理から『イメージ』管理には切り替えられるが、いちど『イメージ』管理にすると『ベースライン』管理には戻せない 7.0 の時点では、NSX や vSphere with Kubernetes を含むクラスタは『イメージ』での管理は行えない 『イメージ』を利用したホストの修正を行うと、それらのエージェントが削除される ファームウェアの管理まで行う場合、ハードウェアベンダから提供される Hardware Support Manager プラグインの導入が必要 現時点では Dell か HPE のみの模様 Dell であれば OpenManage Integration for VMware vCenter （OMIVV）、HPE であれば iLO Amplifier が必要で、それぞれ仮想アプライアンスとして構築する などなど、種々の注意事項や制約があるようです。ドキュメントをしっかり読まないとハマりそうですね。\nなお、従来はパッチやドライバの最小単位は VIB でしたが、7.0 からはコンポーネントなる概念に代わっています。コンポーネントには、一つ以上の VIB を論理的にグループにしたものです。この辺もドキュメントに書いてあるので、作業中の用語に混乱しそうな場合は確認しておくとよさそうです。\n今回の作業 今回は、最終的に『ESXi 7.0 を vLCM でイメージ管理できる』状態を目標に、以下を行っていきます。\nベースラインを利用して、ESXi 6.7 を 7.0 にアップグレードする イメージでの管理に切り替える その過程で、最新のパッチをベースにしたイメージを作成する イメージを利用して最新のパッチを適用する ESXi 6.7 の 7.0 へのアップグレード まずは ESXi を 6.7 から 7.0 にアップグレードします。イメージによる管理は ESXi 7.0 以降でないと行えないため、アップグレードはベースラインを利用して行うことになります。\nvLCM のメニュは、vSphere Client に完全に統合されています。というか、もともと VUM だった項目が vLCM になっています。\nまずは、アップグレード用のベースラインを作る準備として、[インポートされた ISO] タブから、元になる ISO ファイルをインポートします。\nインポートできました。\n続いて、ベースラインを作成します。[ベースライン] タブで [新規] \u0026gt; [ベースライン] から、インポートした ISO ファイルを利用したアップグレードベースラインを作ります。\nできました。\n作成したベースラインをクラスタ（かホスト）に添付します。\nデフォルトでは、事前定義された二つのベースライン（重要なホストパッチ、ホストセキュリティパッチ）が最上位の vCenter Server オブジェクトに添付されていて、クラスタやホストにもこれが継承されていますが、今回は事前に分離してから作業しています。\n添付すると、アップグレード前なので、非準拠と判定されます。\nここから、ベースラインに合わせる修正作業をしていきます。今回はクラスタに対してまとめて修正を実行しましたが、一台ずつ実施したい場合はホスト単位でももちろん構いません。\n修正のための設定をして、続行します。\n今回は [今すぐ] にしたので、直ちに適用が始まりました。クラスタ内のホストで 1 台ずつ、\n対象ホスト上の稼働中の仮想マシンの移行 対象ホストのメンテナンスモードへの切り替え アップグレード 対象ホストのメンテナンスモードの終了 が行われていきます。サポートされていないハードウェア（NUC8i5BEK）を使っていますが、特に問題なくすんなりとアップグレードされて起動してきました。\n仮想マシンが共有ストレージ上にない場合は、Storage vMotion まではしてくれないので、そのホストのアップグレードはスキップされます。その場合、手動で仮想マシンを逃がすなど対処してから再度修正処理を実行します。\n最終的に、クラスタ内の全台がベースラインに準拠した状態になったら、アップグレードは完了です。\nなお、アップグレードが完了した段階で、ESXi のライセンスが評価モードに切り替わっています。必要に応じて、新しく vSphere 7.0 のライセンスキーを追加し、ホストに適用してください。\nベースラインからイメージへの切り替え クラスタのホスト全台が ESXi 7.0 以上になったので、ベースライン管理からイメージ管理に切り替えます。\n事前準備として、ベースイメージを取り込みます。オフラインバンドルの ZIP を取り込んでもよいですが、今回はインタネットから取得するため、vLCM の設定画面から、以下の URL の利用を有効化します。\nhttps://hostupdate.vmware.com/software/VUM/PRODUCTION/main/vmw-depot-index.xml 有効化後、[アクション] \u0026gt; [更新の同期] を実行して、[デポのイメージ] タブにベースイメージのリストが表示されていることを確認します。執筆時点では 7.0.0b と 7.0.0bs がありますが、違いは VMware のブログ でも説明されています。下部では、ベンダアドオンなども確認できますね。\nデポにイメージが表示されたら、クラスタをベースラインからイメージに切り替えていきます。\nクラスタの [アップデート] \u0026gt; [イメージ] で、[イメージのセットアップ] からウィザードを開始します。\n今回は、アップグレードの残骸（古い fdm の VIB、おそらく今は無効にしている HA をかつて有効にしていた影響）がスタンドアロン VIB として検出されてしまいました。もし今後もこの VIB を導入したままにしたい場合は、この VIB を含むコンポーネントをイメージに含める必要がありますが、今回はただの残骸で不要なので無視します。\nここで ESXi のベースイメージと、任意でベンダアドオンやファームウェアなども選択します。Dell や HPE など、きちんとサポートされるハードウェアを使っている場合は、このあたりから必要な追加コンポーネントを指定することになります。\nサポートされているハードウェアを使っていて、かつ、Harware Support Manager が事前に構成されている場合は、あらかじめハードウェアとファームウェアの組み合わせなどを定義しておくことで、ここでファームウェアなどの構成もイメージに含められるようです。\n今回はもちろんないので、ミニマムで ESXi のバージョン（執筆時点で最新の 7.0.0b）だけを指定して保存します。\nイメージを保存すると、コンプライアンスの確認が走ります。今回は、次の 2 点が検出されていますが、どちらも想定内です。\nホストのバージョンがイメージと異なる スタンドアロンの VIB（前述した残骸）がインストールされている どちらも、このあとの適用作業で修正されます。後者はつまり削除されるわけですが、このように、イメージに含まれない余計なモノが入っていると削除される点は、かつてのベースラインでの管理とは異なる、イメージでの管理の特徴的なところの一つだと思います。\n続行すると、イメージ管理への切り替えは完了です。\nイメージに準拠するようホストを修正する クラスタの [アップデート] \u0026gt; [イメージ] の画面で、イメージのコンプライアンスの確認や、事前チェック、修正時の動作に関する設定、実際の修正などが行えます。\n修正の実行前には、事前チェックをしておくとよさそうです。修正の失敗の原因になる諸々をチェックして教えてくれます。例えば今回の環境だと、DRS が無効で、かつ vCSA が動作しているホストで問題が検出されました。一台ずつ対応すればよいだけなので、気にしないことにします。\nクラスタの全台を修正する場合は [すべて修正] を、ホスト単位で修正する場合はホスト名の左の […] から [修正] を実行します。\n確認画面が出て、続行すると修正が開始されます。ホストで何が行われるのか見せてくれるのは、わかりやすくてよいですね。\n進捗は元の画面で逐次表示されていきます。これもわかりやすくてよいですね。\n途中、停止状態の vCenter Server 6.7（前回のエントリ で作業した残骸で不要なヤツ）が乗っているホストで、vCenter Server が稼働中かつ DRS が無効だから適用できない旨の怒られが発生しました。停止状態でも vCenter Server が居ると（移行できなければ）ダメなようで、不思議な仕様だなあと思いつつ一時的にインベントリから削除して回避しています。\nそんなこんなで順次修正が完了し、イメージを遵守できた状態になりました。めでたしめでたし。\nまとめ vSphere 7.0 からの新機能である vLCM を使って、ESXi のアップグレードと、イメージ管理によるパッチ適用を行いました。\nNSX や vSphere with Kubernetes を構成するホストでは利用できないなど、制約はまだあるものの、イメージによる管理は VUM よりも素直でわかりやすい印象を受けました。\n今回は試しませんでしたが、API を使って JSON ベースでの管理もできるようで、構成管理の負荷を下げる余地はまだまだありそうです。今後の機能追加やサポート範囲の拡大にも期待できそうですね。\n","date":"2020-07-19T16:52:12Z","image":"/archives/3133/img/image-81.png","permalink":"/archives/3133/","title":"vSphere Lifecycle Manager (vLCM) で ESXi を 6.7 から 7.0 にアップグレードして管理する"},{"content":"はじめに 最近 vSphere 7 が出たので、おうちの牧場をアップグレードします。\nこの牧場は、物理的には Intel の NUC（NUC8i5BEK）にインストールした vSphere 6.7 が 2 台で、その片方に vCSA 6.7 が居る状態です。共有ストレージとして QNAP（iSCSI/NFS）がありますが、諸々の事情で vCSA は ESXi のローカルデータストア（M.2 SSD）に配置しています。外部認証ソースも外部 PSC も使っていない、ミニマムな感じです。\nサポートされるアップグレード順序は、KB78221 で説明されています。今回の環境はとてもちまっとしたアレなので、素直に vCenter Server を上げてから ESXi を上げるのみです。\nUpdate sequence for vSphere 7.0 and its compatible VMware products (78221) このエントリでは、vCenter Server のアップグレードだけを扱います。\nESXi のアップグレードもこの後でもちろんしますが、これには VUM の後継である vSphere Lifecycle Manager（vLCM）を使ってみたいので、別エントリとして投稿 しています。\nvSphere 7.0 の理解と準備 根本的に、vSphere 7.0 になって、\nESXi 6.0 以前のサポートが廃止された（6.5 以上が必須） Windows 版の vCenter Server が廃止された 外部 PSC が廃止された（アップグレードの過程で統合される） Flash ベースの vSphere Web Client が廃止された などなど、アーキテクチャレベルでいろいろ変更があります。\nそういう何やかやふくめ、アップグレードにあたっては何はともあれドキュメント読みましょうねという感じなので、KB や製品ドキュメントを一通り見ておきます。\nVMware vSphere 7.0 Release Notes Important information before upgrading to vSphere 7.0 (78487) vSphere 7 Upgrade Best Practices (78205) vCenter Server Upgrade VMware ESXi Upgrade ここからリンクされている KB や関連ドキュメントもざっくり眺めます。マジメなことをいうとそもそも vSphere 7.0 はぼくが使っている NUC への導入をサポートしていませんが、7.0 に限らず昔からそう（アップグレード前の状態でそもそも非サポートな状態）なので気にしないことにします。\nOEM のドライバを使っている場合はどうしろとか、現行バージョンが vCSA の 6.7 U2c か U3 なら事前にコレをやれとか、いろいろ書いてあるので、該当する場合はその通りにするとして、凝ったことををしていない今回のようなシンプルな構成であれば、実際の作業はウィザードをぽちぽちするレベルで終わりです。\nもちろん、現行が外部データベースを使っていたり、めちゃくちゃ古かったり、外部 PSC が居たり、そもそも Windows 版だったり、外部 VUM を立てていたりすると、いろいろ考慮事項も当然でてくるので、その辺りはドキュメントを読んでください。\nvCenter Server のアップグレードプロセス 標準のアップグレード手順に従う場合、処理の流れはだいたいこんな感じです。一連の作業は、ウィザードに従うと自動で順次行われます。\n新しい vCSA インスタンスがデプロイされます。この段階では、IP アドレスは作業用の一時的なもの（作業時に指定）が付与されます 現行の vCSA から、構成情報がエクスポートされます 新しい vCSA にエクスポートされたデータを転送します 現行の vCSA が停止され、現行の vCSA が使っていた IP アドレスや FQDN が新しい vCSA に設定されます エクスポートされたデータを使って、新しい vCSA に構成が復元されます この後、状態を確認して、必要な後処理（これもドキュメントがありますが、典型的にはライセンスの投入など）を行えば、vCenter Server のアップグレードは完了です。\n新しい vCSA を現行の vCSA と同じインベントリに配置する場合、作業の流れの都合上、仮想マシン名は現行の vCSA のものとは別にする必要があります。この場合、もし仮想マシン名も現行のものを維持したいのであれば、あとから手作業で仮想マシン名の変更（と、ファイル名まで気にする場合は Storage vMotion）が必要です。\nvCenter Server のアップグレード やっていきます。今回は Windows の端末上で GUI インストーラを使って作業しました。\nISO をマウントして、./vcsa-ui-installer/win32/installer.exe を叩くとインストーラが立ち上がるので、[Upgrade] を選択します。\nあとは愚直にウィザードに従っていきます。まずは Stage 1 です。\n現行の vCSA の情報を入力して、接続を確認します。認証情報と、現行の vCSA それ自体が動作している ESXi の情報も入力します。\n新しい vCSA のデプロイ先の ESXi の情報を入力します。\n新しい vCSA の仮想マシン名とパスワードを指定します。新しい vCSA が現行の vCSA と同じインベントリに並ぶことになる場合は、現行の vCSA と同じ仮想マシン名だとこの段階で怒られるので、別の名前を入れます。\n新しい vCSA のサイズを指定します。\n新しい vCSA を配置するデータストアを指定します。シンプロビジョニングにしたい場合はチェックも入れます。\n新しい vCSA が利用するポートグループを選択して、バージョンアップ中だけ利用される一時 IP アドレスなどを入力します。\n確認画面で [FINISH] を押下すると、デプロイが始まります。\nデプロイ先に指定した ESXi を見ると、新しい仮想マシンができて、パワーオンされる様子が観察できます。vCSA 7.0 は、Photon OS 3.0 がベースのようですね、vCSA 6.7 は Photon OS 1.0 でした。\nで、終わると、Stage 2 への進め方が案内されます。[CONTINUE] を押下するか、指示された URL にアクセスして続行します。\nここから Stage 2 です。[NEXT] で進むと、アップグレードの事前チェックが開始されます。\nこのアップグレードの事前チェックでエラーがあると先に進めないので、その場合はどうにかして直します。\n今回は、環境固有の構成（6.7 の構築時点で手抜きして DNS サーバを立てずに強引にごまかしていた）の都合上、名前解決関連でエラーになったので、この段階で新しい vCSA に SSH でログインしてごにょごにょして修正しました。この方法は本筋ではないので本エントリの末尾で補足します。\nエラーを解消したあとも警告がいくつか残りましたが、エラーでなく警告であれば、確認の上で続行できます。今回は想定内の警告のみだったので、そのまま進めました。\nアップグレードするデータの種類を選びます。構成情報だけでなく、過去のタスクやイベント、パフォーマンスの統計情報なども移行できるようですが、その分データ量や作業時間は増えるようです。今回は、せっかくなので全部にしました。\nCEIP を構成します。参加すると、表示されている機能が使えるようになります。逆にいえば、参加しないとこれらの機能は使えなくなります。参加状態は後からも変更できます。\n現行 vCSA のバックアップを取得済みであることを宣言して続行します。バックアップ、実は取っていませんでしたが、失敗してもまあヨシな環境なのでそのまま進めました。\n最後の最後に、現行 vCSA が停止する旨の確認がでて、了承すると実際の処理が開始します。\nあとは待ちです。移行のデータは新旧の vCSA 間で直接やりとりされます。作業端末は経由しません。\n移行データのエクスポートがひと通り終わると、現行 vCSA がシャットダウンされ、新しい vCSA の構成やサービスの起動、移行データの適用が始まります。この段階から、新しい vCSA が旧 vCSA の IP アドレスなどを持つようになります。\n完了しました。\nアップグレード後の確認と後処理 完了後、vSphere Client の URL にアクセスすると、確かに HTML5 版のみになっていました。\n見た目や操作感は vSphere 6.x の頃とほとんど変わりません。\n移行作業中に指定した通り、設定やインベントリ情報だけでなく、過去のタスクやイベント、パフォーマンスの統計情報などもきれいに残っています。\nただ、すぐに目に入るだけでも、\nvCenter Server のライセンスが評価モードに変わっている 旧 vCSA の仮想マシンが残っている 新 vCSA の仮想マシン名は作業中に指定したもののままだ あたりは対処が必要そうです。\nライセンスキーは 6.x と 7.x で違うので再適用が必要なのは当然ですが、仮想マシン名は、実体のファイル名まで気にする場合は Storage vMotion でデータストアを移行しないといけないので、ちょっと注意ですね。\nvCenter Server の最新パッチ適用 アップグレードが終わったので、アップデートをしようとしました。\nvSphere Client からは、Update Planner を利用して、アップデート（パッチ）の有無や適用の事前チェック結果などを確認できます。ただし、この Update Planner は、CEIP に参加していないと使えない（エラーが出る）ようです…… 謎仕様ですね。\n既定では、vCSA に接続された ISO ファイルの中身と VMware の公開リポジトリがアップデートの取得先として探索されます。定期的なアップデートの自動チェックも設定できます。\nトラブル： アップデートが進まない で、実際の適用は、VAMI から数クリックで行える…… はずなので、VAMI にログインして、最新のアップデートをいちどステージングして、その後インストールを開始したところ、\nプログレスバーが『インストールが進行中です』で 0 % からまったく進まない 別セッションで VAMI にログインしようとすると『アップデートの進行中です』が表示されてログインできない パフォーマンスチャートを見ても何も動いていなさそう な状態になってしまいました。予想ダウンタイムが 254 分というのもにわかには信じがたい数字です。\n解決策： VAMI を使わない 調べるとどうも KB があります。\nAccessing the VAMI returns error “Update installation in progress” after recovering from a failed update (67179) 内部でいちどコケていたのか何なのか、進捗がうまく更新されなかったみたいですね。素直に KB の通りにやってみます。\n# cat /etc/applmgmt/appliance/software_update_state.conf { \u0026#34;state\u0026#34;: \u0026#34;INSTALL_IN_PROGRESS\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;7.0.0.10400\u0026#34;, \u0026#34;latest_query_time\u0026#34;: \u0026#34;2020-07-18T16:04:05Z\u0026#34;, \u0026#34;operation_id\u0026#34;: \u0026#34;/storage/core/software-update/install_operation\u0026#34; } # cp /etc/applmgmt/appliance/software_update_state.conf /tmp/software_update_state.conf.org # service-control --stop applmgmt Operation not cancellable. Please wait for it to finish... Performing stop operation on service applmgmt... Successfully stopped service applmgmt # rm /etc/applmgmt/appliance/software_update_state.conf # service-control --start applmgmt Operation not cancellable. Please wait for it to finish... Performing start operation on service applmgmt... Successfully started service applmgmt # cat /etc/applmgmt/appliance/software_update_state.conf { \u0026#34;state\u0026#34;: \u0026#34;UP_TO_DATE\u0026#34; } これで、たしかにログインできるようになりました。GUI で見る限りではインストール前の状態に戻っています。\nが、ふたたび GUI からインストールをしようとしたら、再発しました……。\n仕方ないので、再度復旧させて、今度は CLI でアップデートを試してみます。見てみると、そもそもステージングが正しくされていない模様。\nCommand\u0026gt; software-packages list --staged [2020-07-19T02:07:58.201] : Packages not staged GUI の表示がおかしそうなので、GUI からはいちどステージングを解除して、\nステージング自体も今度は CLI から行います。\nCommand\u0026gt; software-packages stage --url --acceptEulas [2020-07-19T02:10:46.201] : Not running on a VMC Gateway appliance. ... [2020-07-19T02:10:47.201] : Staging process completed successfully Command\u0026gt; software-packages list --staged [2020-07-19T02:11:16.201] : category: Bugfix ... version: 7.0.0.10400 updateversion: True allowedSourceVersions: [7.0.0.0,] buildnumber: 16386292 ... 目的のモノがステージングされてくれたので、インストールします。\nCommand\u0026gt; software-packages install --staged [2020-07-19T02:14:52.201] : Validating software update payload [2020-07-19 02:14:52,670] : Running validate script..... [2020-07-19T02:14:53.201] : Validation successful [2020-07-19 02:14:53,680] : Copying software packages 127/127 [2020-07-19 02:17:10,915] : Running system-prepare script..... [2020-07-19 02:17:12,928] : Running test transaction .... [2020-07-19 02:17:14,945] : Running prepatch script..... [2020-07-19 02:18:42,103] : Upgrading software packages .... [2020-07-19T02:20:09.201] : Setting appliance version to 7.0.0.10400 build 16386292 [2020-07-19 02:20:09,265] : Running patch script..... [2020-07-19 02:26:47,947] : Starting all services .... [2020-07-19T02:26:48.201] : Services started. [2020-07-19T02:26:48.201] : Installation process completed successfully 10 分くらいで終わりました。254 分とは……？\n無事に最新になっています。\nCommand\u0026gt; com.vmware.appliance.version1.system.version.get Version: Version: 7.0.0.10400 Product: VMware vCenter Server Appliance Build: 16386292 Type: vCenter Server with an embedded Platform Services Controller Summary: Patch for VMware vCenter Server 7.0.0 Releasedate: June 23, 2020 Installtime: 2020-07-18T22:37:32.988Z そんなわけで、もしかすると vCSA のアップデートは VAMI より CLI のほうがトラブルを招きにくいかもしれませんね。そのうち修正されるだろうとは思いますが……。\nまとめ vSphere 6.7 環境のうち、vCenter Server を 7.0 にアップグレードし、パッチの適用も行いました。\nアップグレードプロセスや全体的な使用感も 6.7 とは大きく変わっていないので、これまで通り安心して使えそうです。サクサク動いてくれて快適です。\n次のエントリ では、vLCM を利用した ESXi のアップグレードとパッチ管理を行っていきます。\n補足： vCSA 自身を vCSA 用の DNS サーバにする アップグレード前、つまり 6.7 の構築時点で手抜きをしていたので、今回の環境内には DNS サーバがありません。このため、アップグレードの Stage 2 の冒頭の事前チェックで、新しい vCSA から現行の vCSA のホスト名が解決できずにエラーになりました。\nアップグレード前の vCSA 6.7 は、DNS サーバがなくても FQDN で構成できるようにするため、vCSA 自身を vCSA 用の DNS サーバにしていました。具体的には、\nvCSA の /etc/hosts にエントリを追加する vCSA で動作している Dnsmasq に /etc/hosts を参照させる ことをしていました。なお、自宅のラボなので好きにやっていましたが、もちろん非サポートです。\n同じことは vCSA 7.0 でも有効なようですし、DNS サーバをわざわざ立てるのもアレなので、今回もこれを行いました。もちろん非サポートです。\nStage 1 のあと、かつ Stage 2 の前であれば、すでに vCSA への SSH ができる状態なので、指定していた一時 IP アドレスに接続して、/etc/hosts に必要なエントリを追記します。\n# vi /etc/hosts # cat /etc/hosts ... 192.168.0.201 kuro-vcs01.krkb.lab kuro-vcs01 192.168.0.200 kuro-qnap01.krkb.lab kuro-qnap01 192.168.0.202 kuro-esxi01.krkb.lab kuro-esxi01 192.168.0.203 kuro-esxi02.krkb.lab kuro-esxi02 この後、Dnsmasq の設定ファイルを修正して、Dnsmasq が /etc/hosts のエントリを DNS クエリへの応答に利用できるようにします。no-hosts をコメントアウトして、オマケで bogus-priv を足すだけです。\n# sed -i \u0026#39;s/^no-hosts/#no-hosts\\nbogus-priv/\u0026#39; /etc/dnsmasq.conf # cat /etc/dnsmasq.conf listen-address=127.0.0.1 bind-interfaces user=dnsmasq group=dnsmasq no-negcache #no-hosts bogus-priv log-queries log-facility=/var/log/vmware/dnsmasq.log domain-needed dns-forward-max=150 cache-size=8192 neg-ttl=3600 この後、Dnsmasq を再起動します。\n# systemctl restart dnsmasq これで、OS がホスト名の名前解決を試みたときに、透過的に /etc/hosts のエントリも応答されるようになりました。dig や nslookup で確認できます。\n# dig kuro-esxi01 ... ;; ANSWER SECTION: kuro-esxi01. 0 IN A 192.168.0.202 ;; Query time: 0 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ... vCSA（というか Photon OS）は、名前解決時の第一参照先が（/etc/resolv.conf で） 127.0.0.1:53 になっています。vCSA の場合は、このポートで Dnsmasq が待ち受けているため、上記の手順で名前解決に透過的に /etc/hosts が利用できたということです。\nなお、DNS サーバがなくても、すべて FQDN でなく IP アドレスを指定してデプロイすれば、上記のような面倒なことをしなくても構築は可能です。\n追記 後日、上記の 7.0.0.10400（7.0.0b）からさらに新しい 7.0.0.10600（7.0.0c）にアップデートした際は、GUI からの操作ですんなり完了できました。\n","date":"2020-07-18T17:51:47Z","image":"/archives/3077/img/image-41.png","permalink":"/archives/3077/","title":"vCenter Server を 6.7 から 7.0 にアップグレードする"},{"content":"**EdgeX Foundry の初修者向けハンズオンガイド（日本語）**を作成して公開しました。\nいわゆる セルフペースドラボ形式 です。Docker がインストールされた Ubuntu があれば、実際に EdgeX Foundry を動作させながら、基本的な アーキテクチャや利用方法を学習できる ように構成しています。\n併せて、先日の EdgeX Foundry Tokyo Meetup #2 でこのガイドを ハンズオンセッションとして解説しながら実践（75 分） したときの録画が公開されています。\nコンテンツ 📖 EdgeX Foundry ハンズオンラボガイド本編 🎥 EdgeX Foundry Tokyo Meetup #2 録画（1.94 GB） (参考) EdgeX Foundry Tokyo Meetup #2 イベントページ (参考) Meetup and Event Resources - EdgeX Wiki 説明など これまで、EdgeX Foundry 関連のエントリを趣味で好き放題にいくつか書いてきていました が、それをきっかけに、ハンズオンセッションへのお声かけをいただきました。\nブログの内容がすでにハンズオンっぽくなっていたものの、やさしくない表現や最新の状況と整合していない部分も多々あったため、ブログを基に再編しガイドとしてまとめたのが本ガイドです。コミュニティの承認やレビュは得ていないので、非公式ですが……。\nハンズオンセッションは、実際にこのガイドに従って、解説を加えながら実機を操作する様子を配信する形で行いました。\nおかげさまで 40 名ほどのご参加をいただき、オンラインでのハンズオンセッションというなかなかない機会になり、個人的にもよい経験になりました。おもしろかったです。\n今後のこと ガイド執筆時点では Fuji が最新リリースでしたが、今月には Geneva、半年後には Hanoi、と今後も半年ごとに新しいリリースが予定されています。\n趣味の範囲内ではあるものの、何らかの形で追従できたらいいなあとは考えています。まずは Geneva を触るところからですね。\nEdgeX Foundry 関連エントリ 冬休みの自由研究： EdgeX Foundry (1) 概要 冬休みの自由研究： EdgeX Foundry (2) 導入とデータの確認 冬休みの自由研究： EdgeX Foundry (3) エクスポートサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (4) MQTT デバイスサービスの追加 冬休みの自由研究： EdgeX Foundry (5) ルールエンジンによる自動制御 冬休みの自由研究： EdgeX Foundry (6) アプリケーションサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (7) Kubernetes 上で Fuji リリースを動かす EdgeX Foundry ハンズオンラボガイド公開 EdgeX Foundry： Geneva から Hanoi へ ","date":"2020-05-17T08:20:20Z","image":"/archives/3065/img/index-logo.png","permalink":"/archives/3065/","title":"EdgeX Foundry ハンズオンラボガイド公開"},{"content":"愛用の真空管ヘッドホンアンプ ValveX-SE のノイズが気になるようになってきた、というか、前からずっと気になっていたので、真空管を交換しました。買うにあたってなんとなく調べはしたけれど、真空管の世界は本当にもうよくわからない。\n大学生だった頃に HD650 と一緒に買ったものですが、製造元がすでになくなってしまっているので、今では中古以外では手に入りません。10 万円以下クラスのヘッドホンアンプでは、当時はけっこう有名でしたね。\n交換前後の違いがわかるとおもしろいので、交換前の無音状態をまず記録しました。波形はこれです。\n無です。\nでも縦軸をめっちゃ拡大すると、ノイズっぷりが見えます。プツプツというかワシャワシャというかガサガサというか、そういう感じで聴こえます。\n左右ともノイズはあるものの、とくに右側（チャンネル 2）が顕著でした。ひとまず、真空管起因のノイズであることを断定するため、分解して左右の真空管を入れ替えてみます。\nかわいい。\n真空管の左右を入れ替えたら、ノイズも左右が入れ替わったので、真空管が原因といってよさそうです。回路の別のところでなくてよかった。\nもともと使われていた真空管は、Electro Harmonix の 12AU7A/ECC82 EH というモノでした。\n同じものもまだ買えそうですが、在庫がなさそうだったのと、そしてちょっとくらい沼を覗いてみてもいいじゃない？ という気持ちに嘘をつけず、そんなわけで違うどれかを買うことにしました。\n……という動機で調べはじめたら、型の互換だのマッチングだの単語はわからないし、12AU7（ECC82）系はメジャな型で互換品もたくさんあるみたいで、本当にもう何が何やらわからなくなりました。\n困ったので、もうひと踏ん張りいろいろ見て調べて考えてから、とりあえず高いヤツを適当に買っておきました。\nオーディオ関係で迷ったときのぼく『よくわかんないから高いほうにしとこ』\n\u0026mdash; くろい (@kurokobo) April 4, 2020 沼の先住民の先生にも（たぶん）ポジティブな評価をいただけたのでよしとします。\nプリアンプは組んだことないので12AU7は使ったことないのだけど、プリ部ではメジャーな玉ですよね。12AU7Aは元々ハイグレード品なので互換性があって同じくハイグレード品のECC802Sに差し替えるのはアリだと思いますよ。秋葉原のそれ系のお店に行くとビンテージ管も売ってます。\n\u0026mdash; Kaz Takashio【研究科いいんちょ… なう】 (@kaz_ss597) April 4, 2020 買ったのは JJ の ECC802S GOLD というものです。\n箱にはなにがしかの測定結果が書いてある。\n二本組を買ったので、よくわからないけれど、この二本の何らかの特性が組み合わせて使うのに適しているのだということにします。Amazon の商品種別の MP て、Matched Pair ですかね。わからん。\nとりあえずかわいいので記念撮影。\n交換して、ノイズはなくなりました。上が交換前、下が交換後。\n前後の測定条件が厳密ではないのと、あと電源由来のなにかもあって完全ではないけれど、聴覚上はばっちり問題なくなったのでよしとします（二回目）。\nなお、音の違いはわかりません。\n","date":"2020-05-10T10:08:25Z","image":"/archives/2972/img/DSC06486.jpg","permalink":"/archives/2972/","title":"ValveX-SE と真空管"},{"content":"HDMI のキャプチャのことをいろいろ調べていたら おもしろそうなブログ を見つけたので、実際に遊んでみました。これはその記録です。\n免責 安定性は不明 です、実験程度 に捉えてください 当然ながら ハードウェア HDMI キャプチャのほうがよい です。もしくはプラグインを書くべきです 映像は 実測で 0.5 秒ほど遅延 しますし、画質は落ちます し、CPU をとても食い ます 音声は得られません 何かが起きても ぼくは責任は取りません TL; DR 手順 Lenkeng の HDMI 延長器（LKV373）の送信機（約 5,000 円）に任意の HDMI 出力を突っ込む 受信機の代わりにソフトウェア golkv373 でデータを受け取ってデコードし、Motion JPEG としてストリームする Reverse engineering Lenkeng HDMI over IP extender | danman\u0026rsquo;s blog OBS Studio で Motion JPEG を映像ソースとして取り込む 映像を OBS Studio で仮想カメラ化する 結論 使えないことはないけれど、CPU をほんとうに食うし、0.5 秒くらい遅延するので、この方法が活用できるシーンは限られそうだ 素直にハードウェア HDMI キャプチャを使うか、LKV373 を使うにしてもプラグインを書く方がよいだろう 概要 できることと注意 この方法によって、\nPC やゲーム機の映像を、YouTube Live などの生配信で使う デジタル一眼レフカメラの映像を Zoom や Teams や Meets で使う などが、ハードウェアの HDMI キャプチャを用意するよりは比較的低コスト（約 5,000 円）でできるようになります。\nただし、端的に言って 品質を相当犠牲にする ことになり、\nCPU をめっちゃ食う 映像が 0.5 秒くらい遅延して配信される 音声は別に工夫（分離器で音だけ取り出す、コードを追加する、など）が必要 安定して動作するかどうか誰もわからない な点には注意が必要です。\n別解 単に デジタル一眼レフカメラの映像を Web カメラ化したいだけ であれば、macOS なら Camera Live と CamTwist、キヤノン製のカメラなら EOS Webcam Utility など、より低コストかつ理想的な別解があります。\nまた、LKV373 を使う場合でも、macOS であれば、別の方のブログで OBS Studio のプラグインを実装している偉大な例があります。\nOBS Studioで使えるHDMIキャプチャを5000円で手に入れる KOBA789/obs-lenkeng で、Windows でソニーの α7iii なぼくはこれが使えず、とはいえ、OBS Studio のプラグイン作りはいきなり手を出すにはしんどかったので、手間がかからない代わりに CPU にはめっちゃ優しくない 方法でがんばる、のが本エントリです。\n必要なもの Lenkeng の HDMI 延長器 LKV373（またはその OEM 品）の、送信機（TX、Sender） 受信機は不要 Amazon だと これ とか。約 5,000 円 後継の LKV373A だと使えないのでややこしい 本体の裏に V2.0 があれば LKV373（使える）、V3.0 だと LKV373A（使えない）らしい？ PC の有線 LAN ポート golkv373 VLC media player OBS Studio キモはいちばん上のこれです。\n裏の V2.0 が大事みたいです。\n手順 配線 HDMI ソースと LKV373（TX）をつなぎ、LKV373（TX）と PC を LAN ケーブルで直結します。\nLKV373（TX）がデフォルトで 192.168.168.55/24 を持っているので、PC 側の NIC には同じセグメントの適当な IP アドレスを固定で割り当てます。LKV373（TX）の IP アドレスは Web の管理画面から変えられますが、その場合は、PC 側も併せて変えます。\ngolkv373 の準備 golkv373 のバイナリをダウンロード してきて、起動します。自分でビルドしてもよいです。署名がどうのこうので怒られたら、納得の上でとにかく実行します（ダブルクリックで起動せずにコマンドプロンプトや PowerShell から叩けば怒られませんが、どちらでもよいです）。\nファイアウォールの穴あけも必要です。LKV373（TX）と接続している NIC のプロファイルを Get-NetConnectionProfile かなにかで調べて、そのプロファイルで通信を許可します。\n起動するとなんやかや出力されて、\n2020/xx/xx xx:xx:xx Program started as: [C:\\...\\golkv373-v0.5.2-win64.exe] [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. 2020/xx/xx xx:xx:xx No active transmitters [GIN-debug] [WARNING] Running in \u0026#34;debug\u0026#34; mode. Switch to \u0026#34;release\u0026#34; mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] GET /src/:IP/frame.mjpg --\u0026gt; main.main.func2 (4 handlers) [GIN-debug] GET /src/:IP/frame.jpeg --\u0026gt; main.main.func3 (4 handlers) [GIN-debug] GET /src/:IP/ --\u0026gt; main.main.func4 (4 handlers) [GIN-debug] GET /status --\u0026gt; main.main.func5 (3 handlers) [GIN-debug] GET / --\u0026gt; main.main.func6 (3 handlers) [GIN-debug] Listening and serving HTTP on :8080 続けて毎秒のログが出始めるはずです。状況に応じて出方が変わります。\nファイアウォールに阻まれているなど、LKV373（TX）と疎通できていない状態のときは、No active transmitters だけが繰り返し出力されます。この場合、設定を見直してどうにかします。\n... 2020/xx/xx xx:xx:xx No active transmitters 2020/xx/xx xx:xx:xx No active transmitters 2020/xx/xx xx:xx:xx No active transmitters ... 疎通できているけれども映像が入力されていない場合は、keepalive sent と No active transmitters が両方出力されます。この場合は HDMI のソースに何の信号も来ていない可能性があります。\n2020/xx/xx xx:xx:xx keepalive sent to 192.168.168.55:48689 2020/xx/xx xx:xx:xx No active transmitters 2020/xx/xx xx:xx:xx keepalive sent to 192.168.168.55:48689 2020/xx/xx xx:xx:xx No active transmitters 2020/xx/xx xx:xx:xx keepalive sent to 192.168.168.55:48689 2020/xx/xx xx:xx:xx No active transmitters 疎通できていて映像も送られてきているときは、フレームレートなどが表示されます。\n2020/xx/xx xx:xx:xx keepalive sent to 192.168.168.55:48689 2020/xx/xx xx:xx:xx 192.168.168.55: MB/s=2.66 FPS=30.00 lost=0 2020/xx/xx xx:xx:xx keepalive sent to 192.168.168.55:48689 2020/xx/xx xx:xx:xx 192.168.168.55: MB/s=2.69 FPS=30.00 lost=0 2020/xx/xx xx:xx:xx keepalive sent to 192.168.168.55:48689 2020/xx/xx xx:xx:xx 192.168.168.55: MB/s=2.71 FPS=30.00 lost=0 ブラウザで http://localhost:8080/ にアクセスして、LKV373（TX）の IP アドレス（デフォルトなら 192.168.168.55）を選択すると、転送されてきている映像が表示されます。単に映像が観たいだけであれば、これで終わりです。\nこの段階で、192.168.168.55 からの映像は、\nhttp://localhost:8080/src/192.168.168.55/frame.mjpg で Motion JPEG としてストリーミングされています。ここから、OBS Studio でこの Motion JPEG を映像ソースとして取り込めるように設定していきます。\nプレイリストファイル（XSPF）の作成 OBS Studio では、メディアソース で Motion JPEG の URL を直接指定しても映像は取り込めます。が、\nバッファされるので遅延が増えるうえ、バッファの設定を変えられない 遅延が蓄積していく（ように見える） ので、URL だけでなくキャッシュ設定も含まれたプレイリストファイルを作って、それを OBS Studio から読ませる方法を取ります。\nプレイリストの作成には、VLC media player を使います。VLC media player を起動して、\n[メディア] \u0026gt; [ネットワークストリームを開く] を開き、 URL に http://localhost:8080/src/192.168.168.55/frame.mjpg を入力して、 [詳細オプション] で [キャッシュ] を [0 ms] にして [再生] します。この段階で遅延がひどかったりフレームレートが低かったりする場合は、余計なものを閉じてやり直すなどするとよさげです。\nキャッシュを増やすとより安定しますが、増やしただけ遅延が純増します。ここでは強気に 0 ms です。\nで、映像が極端な遅延なく見えていれば、[メディア] \u0026gt; [プレイリストファイルを保存] して、XSPF ファイルを保存します。この XSPF ファイルがプレイリストで、キャッシュの設定もここに含まれています。\n保存したら、VLC media player はもう要らないので閉じます。\nOBS Studio の設定 最後に、OBS Studio で、ソースとして VLC ビデオソース を追加し、その設定画面で先ほどの XSPF ファイルを追加すれば、完了です。\nここからさらに OBS-VirtualCam などを使って仮想デバイスに出力すれば、別のソフトウェア（Zoom など）でも映像を利用できることになります。\n実測 次のパタンで遅延や CPU 使用率などを比較してみます。\nWeb カメラとして使うパタン 本エントリの方法を使った場合 ハードウェア HDMI キャプチャを使った場合 市販の Web カメラを使った場合 HDMI キャプチャとして使うパタン 本エントリの方法を使った場合 ハードウェア HDMI キャプチャを使った場合 登場人物 PC Intel NUC6i5SYH Intel Core i5-6260U @ 1.80 GHz 32 GB RAM ハードウェア HDMI キャプチャ Ezcap269 市販の Web カメラ Logicool C270 測定方法 ブラウザでストップウォッチを表示 させて、測定パタンに応じた経路で最終的に Zoom に表示します。\n各画面に時間が表示されている状態で、充分に速いシャッタスピード（1/800 程度）で全体を撮影すると、撮影できた各画面の時間の差が遅延時間そのものを表すことになります。\nInter BEE などの展示会では、似た方法をよく低遅延伝送のデモンストレーションで見かけますね。表示させるソースをストップウォッチとフレーム数とで迷いましたが、遅延はミリ秒表記のほうが個人的に馴染みがあるのこうしています。\nWeb カメラとして使うパタン 本エントリの LKV373 を使った方法は、遅延も CPU 使用率も圧倒的に大きくなりました。\n方法 遅延 CPU 使用率 経路 本エントリ（LKV373） 460 ms 程度 80 ～ 100 % ソース → α7iii → LKV373 → OBS Studio → Zoom ハードウェアキャプチャ 150 ms 程度 20 ～ 40 % ソース → α7iii → Ezcap269 → Zoom 市販 Web カメラ 60 ms 程度 20 ～ 40 % ソース → c270 → Zoom そもそも、ある瞬間を α7iii が取り込んで HDMI で出力する（≒ カメラの液晶に表示される）までに 70 ms くらいの遅延があるみたいでした（カメラ側の設定で変わる部分かもですが未調査です）。460 ms の内訳は、\n経路 遅延 ソース → α7iii 70 ms α7iii → LKV373 → OBS Studio 220 ms OBS Studio → Zoom 170 ms な具合です。OBS Studio の仮想カメラでは、デフォルトで入る 3 フレームのバッファ設定をそのままにしているので、その分も含まれるでしょう。\nHDMI キャプチャとして使うパタン カメラを経由しない分遅延は改善されますが、それでも LKV373 を使った方法は、遅延も CPU 使用率も圧倒的に大きいままです。\n方法 遅延 CPU 使用率 経路 本エントリ（LKV373） 370 ms 程度 80 ～ 100 % ソース → LKV373 → OBS Studio → Zoom ハードウェアキャプチャ 80 ms 程度 20 ～ 40 % ソース → Ezcap269 → Zoom 370 ms の内訳は、\n経路 遅延 ソース → LKV373 → OBS Studio 200 ms OBS Studio → Zoom 170 ms な具合でした。\n画質 LKV373 を通すと、JPEG らしさのある圧縮ノイズが視認できるレベルで乗ってきます。\n自分のブログを映した別 PC の画面を LKV373 経由で OBS Studio に取り込んで等倍キャプチャしたものがこれです。\nハードウェア HDMI キャプチャだとこういう劣化はなく、ここにさらに配信プラットフォーム側の圧縮が乗ってくることになるので、画質面もこの方式のデメリットです。\nまとめ 強引に多段変換してどうにかする手法では、遅延も CPU 使用率も大きくなるというごく当たり前のことが確認できました。\n同じ LKV373 を使う手法でも、先の macOS 用の OBS Studio のプラグイン を使うのであれば、原理的にはオーバヘッドは相当減らせると推測できるため、プラグインの開発に至ったモチベーションも合理的なものと言えそうです。\nCPU 使用率の高騰が許容できるなら、例えば録画や生配信の用途であれば、音声に意図的に遅延を入れて同期を取れば使えないこともないかもしれません。一方、オンライン会議などでは、音声を遅延させるわけにもいかず、そうすると声と映像が 0.5 秒ズレることになってしまうので、なかなか使いづらそうです。そしてどちらの場合でも、この方式の安定性はだれにも保証できません。\nいずれにせよ、最近は安いハードウェアキャプチャは 10,000 円強で手に入りますし、予算が許せばそうする方が、あらゆる面で合理的だと思いました。\n","date":"2020-05-05T16:46:12Z","image":"/archives/3030/img/DSC06520.jpg","permalink":"/archives/3030/","title":"デジタル一眼レフカメラを 5,000 円で無理やり Web カメラ化する実験的方法（Windows）"},{"content":"ちょっと前に 4,000 円の怪しいデジタル顕微鏡 を買ったのだけれど、思っていたより遊べてしまい、おもしろかったです。という話です。\n例えば、Raspberry Pi 4 って、\n基盤のチップに文字が書いてあると、読めたり読めなかったりです。\n例えばこれ。何かもじゃもじゃ書いてあるような気がするのだけれど、肉眼では全然読めない。\nそこにこの怪しい顕微鏡を当ててみると、こう。\nもっとがんばるとこう。\nなるほど、QSAK KDD ね、みたいな。意味は知らんけど。\nおもちゃみたいなスタンドも付いているので、がんばって調整すると、撮影もがんばれます。LED 照明が組み込みなのが助かる。\nあとはスマートフォンの画面も、\nドット感がこう。ところどころ汚いのは画面ではなくカメラの問題です、エアダスタでも飛ばせないゴミが常に映る。仕方ない。\nもっと寄るとこう。なるほど、RGB だなあ、光の三原色だなあ、と思える。左側が白、右側が青の部分です。\nお札の小さい文字も読めます。これは諭吉さんの右上です。\nこの顕微鏡は UVC 準拠なので、つなげば適当なソフトウェアで取り込めます。スマートフォンにも Wi-Fi でつなげられるみたいです、試していませんが。\n商品説明では 1,000 倍を謳ってはいるものの、これが実際何倍なのかはよくわかりません。\n画面に常にゴミが映るし、画質は悪いし、ピントも合わせにくいし、解像度は 1280×720 だし、と、欠点は挙げようと思えば挙げられるけれど、それでもお値段を考えるととても優秀でした。たのしい。\nいろいろ分解して遊ぶときに、キラキラしたかっこいい部品を観察しておもしろがるのに役立っています。\n","date":"2020-05-04T07:58:20Z","image":"/archives/3012/img/microscope.jpg","permalink":"/archives/3012/","title":"デジタル顕微鏡で遊ぶ"},{"content":"Docker は マルチ CPU アーキテクチャ をサポートしているので、docker image pull すると、イメージ側が対応していれば、同じイメージの同じタグを指定しても、環境（OS や CPU アーキテクチャ）に応じて適切なイメージが勝手に取得されます。\nで、今回、マルチ CPU アーキテクチャに対応したイメージを、実際に x64 な Windows 環境だけで意外とサクッとビルドできたので、そのお話。\nマルチ CPU アーキテクチャ 例えば alpine:latest を pull して inspect すると、手元の Windows 環境（x64）では次のように amd64 が降ってくるのに対して、\n\u0026gt; docker pull alpine:latest \u0026gt; docker inspect alpine:latest [ { \u0026#34;Id\u0026#34;: \u0026#34;sha256:f70734b6a266dcb5f44c383274821207885b549b75c8e119404917a61335981a\u0026#34;, \u0026#34;RepoTags\u0026#34;: [ \u0026#34;alpine:latest\u0026#34; ], \u0026#34;RepoDigests\u0026#34;: [ \u0026#34;alpine@sha256:9a839e63dad54c3a6d1834e29692c8492d93f90c59c978c1ed79109ea4fb9a54\u0026#34; ], ... \u0026#34;Architecture\u0026#34;: \u0026#34;amd64\u0026#34;, \u0026#34;Os\u0026#34;: \u0026#34;linux\u0026#34;, \u0026#34;Size\u0026#34;: 5612304, \u0026#34;VirtualSize\u0026#34;: 5612304, ... } ] 同じことを Raspberry Pi にインストールした Ubuntu（arm64）で実行すると、CPU が ARM アーキテクチャなので正しく arm64 が降ってきます。\n$ docker pull alpine:latest $ docker inspect alpine:latest [ { \u0026#34;Id\u0026#34;: \u0026#34;sha256:c20d2a9ab6869161e3ea6d8cb52d00be9adac2cc733d3fbc3955b9268bfd7fc5\u0026#34;, \u0026#34;RepoTags\u0026#34;: [ \u0026#34;alpine:latest\u0026#34; ], \u0026#34;RepoDigests\u0026#34;: [ \u0026#34;alpine@sha256:9a839e63dad54c3a6d1834e29692c8492d93f90c59c978c1ed79109ea4fb9a54\u0026#34; ], ... \u0026#34;Architecture\u0026#34;: \u0026#34;arm64\u0026#34;, \u0026#34;Os\u0026#34;: \u0026#34;linux\u0026#34;, \u0026#34;Size\u0026#34;: 5357453, \u0026#34;VirtualSize\u0026#34;: 5357453, ... } ] Docker Hub で見ると、ひとつのタグに対してアーキテクチャごとに別々のイメージが紐づけられている様子がわかります。\n今回は、つまり、これを自分で作りたいね、ということです。\n困ったこと Docker Hub にあるイメージのうち、公式に展開されているものは、もともとこのような仕組みで（または別のタグ、別のイメージとして）複数の CPU アーキテクチャに対応してくれているものが多いのですが、野良イメージだとそうでない場合があります。\nで、今回、実際に amd64 環境で便利に使っていたコンテナを arm64 で動かそうとしたら動かせなかった…… という事があり、そんなわけで自前でビルドすることにしました。\nそのイメージの Dockerfile は公開されていたので、Raspberry Pi 上で docker build . すれば使えはするのですが、ビルドのためだけにいちいちそのアーキテクチャのプラットフォームを用意するのも長期的にみると相当イケてないでしょうということで、21 世紀だしエミュレートしてどうにかできるでしょ、みたいな。\n方法 さっきのリンク先 に Buildx というのが書いてあり、それでできるみたいです。現時点では Experimental な機能 なので、覚悟して使いましょう。\nDocker Desktop for Windows を使う場合、設定画面の Command Line から、Enable experimental features を有効にする必要があります。\nこれで docker buildx コマンドが使えるようになるので、あとは さっきのリンク先 の手順通りです。\n最初はデフォルトのビルダ default が指定されていますが、これだとマルチ CPU の機能は対応しておらず、multiple platforms feature is currently not supported for docker driver と怒られます。\n\u0026gt; docker buildx ls NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS default * docker default default running linux/amd64, linux/arm64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6 \u0026gt; docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t kurokobo/demo:latest . multiple platforms feature is currently not supported for docker driver. Please switch to a different driver (eg. \u0026#34;docker buildx create --use\u0026#34;) ので、BuildKit ベースの新しいビルダを作って、それを使うように設定します。ls したときの * がアクティブなやつです。\n\u0026gt; docker buildx create --name multipfbuilder --use multipfbuilder \u0026gt; docker buildx ls NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS multipfbuilder * docker-container multipfbuilder0 npipe:////./pipe/docker_engine inactive default docker default default running linux/amd64, linux/arm64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6 あとは Dockerfile のあるディレクトリを指定してビルドするだけです。--push を指定して、できたイメージは直接リポジトリにつっこみます。初回実行時は BuildKit のコンテナイメージの取得が最初に入ります。\n\u0026gt; docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t kurokobo/demo:latest --push . [+] Building 59.5s (13/13) FINISHED =\u0026gt; [internal] booting buildkit 16.1s =\u0026gt; =\u0026gt; pulling image moby/buildkit:buildx-stable-1 14.5s =\u0026gt; =\u0026gt; creating container buildx_buildkit_multipfbuilder0 1.6s ... =\u0026gt; [linux/amd64 internal] load metadata for docker.io/library/alpine:3.9 13.1s =\u0026gt; [linux/arm/v7 internal] load metadata for docker.io/library/alpine:3.9 10.2s =\u0026gt; [linux/arm64 internal] load metadata for docker.io/library/alpine:3.9 13.1s =\u0026gt; [linux/amd64 1/2] FROM docker.io/library/alpine:3.9@sha256:414e0518bb9228d35e4cd5165567fb91d26c6a214e9c95899e 9.8s ... =\u0026gt; [linux/arm64 1/2] FROM docker.io/library/alpine:3.9@sha256:414e0518bb9228d35e4cd5165567fb91d26c6a214e9c95899 11.8s ... =\u0026gt; [linux/arm/v7 1/2] FROM docker.io/library/alpine:3.9@sha256:414e0518bb9228d35e4cd5165567fb91d26c6a214e9c95899 9.5s ... =\u0026gt; exporting to image 14.4s =\u0026gt; =\u0026gt; exporting layers 0.4s =\u0026gt; =\u0026gt; exporting manifest sha256:2ce6c278a94742c9510b15369b72bf62681741e52074c0b11c4c1da8706b1417 0.0s =\u0026gt; =\u0026gt; exporting config sha256:9913143b883435a3a206777147d7c9aaac67a50e4538b79283eb1dca6a3ef633 0.0s =\u0026gt; =\u0026gt; exporting manifest sha256:5935a9dde92945f9e4f601cc548918de5076856f7bc06b2137bf0767c5d19978 0.0s =\u0026gt; =\u0026gt; exporting config sha256:93d0264837f8f039cd002e842511276aaf9ff3355dea4f7171ce6a97b4c0264f 0.0s =\u0026gt; =\u0026gt; exporting manifest sha256:e023fb745782f5d2eeda3a8dd80fc93ca0f7051a78ecd03a062d8595473ed7b1 0.0s =\u0026gt; =\u0026gt; exporting config sha256:f5f2bad0db136c6f06bf2d4244814660cf229e66bf9dd18eeb47e958c6033cd6 0.0s =\u0026gt; =\u0026gt; exporting manifest list sha256:9a058a80760a142ff335305a438dfbb0eb640d910a01e9ee8a319ec1faa00a85 0.0s =\u0026gt; =\u0026gt; pushing layers 6.8s =\u0026gt; =\u0026gt; pushing manifest for docker.io/kurokobo/demo:latest 7.0s --push を指定しないと、docker images にも表示されませんでした。BuildKit 内に キャッシュができるだけのようです。\nビルドしてそのまま使えるようにするには --load を指定すればよさそう…… なのですが、現状、--platform に複数の引数を指定している場合は --load はコケる みたいです。実際、コケました。\nBuildKit 自体の機能を正しく使ってキャッシュをどうのこうのとかきちんと何かすればもうすこしいろいろできるのかもですが、まだよくわからんです。\nというわけでできました。\nWindows と Raspberry Pi で実際に pull すると、別のイメージが降ってくるようになりました。かんぺきです。\nなお、できたあとも、手元の Docker ホストには、BuildKit のコンテナ（とそれ用のイメージ）が起動したままで残るようです。使わなくなったら殺そう。\n\u0026gt; docker images REPOSITORY TAG IMAGE ID CREATED SIZE moby/buildkit buildx-stable-1 f2a88cb62c92 2 weeks ago 82.8MB \u0026gt; docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9b06292f089c moby/buildkit:buildx-stable-1 \u0026#34;buildkitd\u0026#34; 2 minutes ago Up 2 minutes buildx_buildkit_multipfbuilder0 応用 GitHub Actions などと組み合わせれば、Dockerfile をコミットしたらこれでビルドして Docker Hub へ…… などもできますね。\n","date":"2020-05-02T10:05:43Z","image":"/archives/2995/img/ss10.png","permalink":"/archives/2995/","title":"x64 な Windows で ARM アーキテクチャのコンテナイメージをビルドする"},{"content":"今まで Raspberry Pi 3 Model B+ を使っていましたが、ようやく Raspberry Pi 4 Model B (4 GB RAM) を買いました。\n電源が USB Type-C コネクタだったり、HDMI 出力が Type-D（HDMI Micro）だったり、ちまちまと 3 系と違うんですね。ケーブルの使いまわしができない……。\nちょうど数日前に 2 年ぶりの LTS である Ubuntu 20.04 がリリースされたニュースもありましたし、入れて遊ぼう…… と思ったものの、まだ Docker が 20.04 をサポートしていない みたいです。19.10 が出たときも Docker が追従するまでにちょっとラグがあったので、仕方ないですね。\nUbuntu 20.04 に 19.10 用の Docker を入れて動かす例もインタネットにはありますが、ちょっとお行儀よく使いたい要件があったので、20.04 自体をあきらめて 18.04 を入れました。\n公式のダウンロードページが全部 20.04 に置き換わってしまったので、Raspberry Pi 向けの古いバージョンのバイナリは、cdimage.ubuntu.com から持ってきます。\nUbuntu 18.04 LTS (Bionic Beaver) Ubuntu 19.10 (Eoan Ermine) Ubuntu 20.04 LTS (Focal Fossa) Raspberry Pi Imager 全然知りませんでしたが、Raspberry Pi Imager というめっちゃ便利なのがリリースされていました。最初の SD カードづくりがこれだけで簡単にできるみたいです。\nRaspberry Pi Downloads How to install Ubuntu with the new Raspberry Pi Imager 後者のブログで書かれている通り、最新の Server の LTS と最新の Core しか選べないので、今日時点だと Ubuntu Server はすでに 18.04 は選択肢になく、20.04 しか出てきません。\nIf you select this option, you will be able to choose from the latest LTS (Long Term Support) Ubuntu server images, or the latest Ubuntu Core images for either armhf or arm64 architectures. Canonical updates these images with every new LTS. How to install Ubuntu with the new Raspberry Pi Imager\rよって、古いバージョンの OS を導入したいときは、結局個別にイメージのダウンロードが必要になってしまうわけですが、とはいえ、これ以外にも機能はあり、\nSD カードの FAT32 でのフォーマット 64 GB 以上の SD カードでも問題なく使える 任意のイメージの書き込み 古いバージョンの OS を書き込みたいときに使える Raspberry Pi の EEPROM のリカバリ Raspberry Pi がお亡くなりになったとき用 が地味にうれしい実装です。64 GB 以上の SD カードだと、Windows では FAT32 でフォーマットするために地味に面倒な手間が発生していたので、助かります。\nUbuntu 18.04 の導入 イメージを書き込んで起動すれば普通に起動してくるので、あとはノリでどうにかなりますが、公式のチュートリアルもとても親切ですね、すごい。\nHow to install Ubuntu on your Raspberry Pi で、適当に気合で構成したら、/etc/netplan/99-override.yaml あたりにファイルを作って設定を放り込んで、IP アドレスを固定します。家の Wi-Fi の SSID がステルスなので、StackExchange で投稿されている謎ハック を追加で突っ込んでいます。汚い。\nnetwork: version: 2 ethernets: eth0: dhcp4: false addresses: - 10.96.1.239/24 wifis: wlan0: access-points: \u0026#34;MY_STEALTH_SSID\\\u0026#34;\\n scan_ssid=1\\n #\u0026#34;: password: MY_PASSWORD dhcp4: false addresses: - 192.168.0.239/24 gateway4: 192.168.0.1 nameservers: addresses: - 192.168.0.1 Docker と Docker Compose の導入 Docker を入れるには、淡々とガイドに従います。途中、リポジトリを追加するときの CPU アーキテクチャは arm64 です。\nInstall Docker Engine on Ubuntu Docker Compose を入れるのも、淡々とガイドに従います。arm64 用のバイナリはないので、Alternative Install Options を参考に pip で入れました。入れる前にパッケージがいくつか足りないので足さないとダメでした。\nInstall Docker Compose sudo apt install -y python3 python3-pip sudo apt install -y libffi-dev libssl-dev sudo pip3 install docker-compose というわけで、あとは遊ぶだけです。\n","date":"2020-04-25T16:11:35Z","image":"/archives/2959/img/DSC06514.jpg","permalink":"/archives/2959/","title":"Raspberry Pi 4 と Raspberry Pi Imager"},{"content":"Font Awesome のアイコンが見出しの頭にくっつくようにして、なんとなくかわいい感じにします。\n説明用のライブデモページを作っていますので、詳しくはそちらで。\nデモページ https://kurokobo.github.io/mkdocs-header-awesome/ リポジトリ https://github.com/kurokobo/mkdocs-header-awesome/ 簡単にまとめると、\nextra_css で Font Awesome の CSS ファイルを読ませて カスタム CSS ファイルで ::before 疑似要素で Font Awesome の文字を流し込めばよい Material テーマの場合は、いくつかの設定で !important でオーバライドする必要がある という感じです。\nmkdocs.yml で以下のようにして、\nextra_css: - \u0026#34;https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css\u0026#34; - \u0026#34;css/custom.css\u0026#34; そのうえで、例えば Material テーマの場合は、custom.css で以下のようにします。\n.md-typeset h2::before { display: inline-block !important; font-family: \u0026#34;FontAwesome\u0026#34;; content: \u0026#34;\\f0a9\u0026#34; !important; margin-right: .3em; } 説明用のページでは、Font Awesome の最新の 5.x 系を使う例も紹介しています。\nデモページ https://kurokobo.github.io/mkdocs-header-awesome/ リポジトリ https://github.com/kurokobo/mkdocs-header-awesome/ あと地味に \u0026lt;a\u0026gt; タグも ::before でアイコンを付けています。かわいい。\n","date":"2020-04-03T10:46:08Z","image":"/archives/2952/img/78280305-d5ec7400-7553-11ea-9e74-8530b7a9fdba.png","permalink":"/archives/2952/","title":"MkDocs の見出しを Font Awesome で装飾する"},{"content":"Chromebook、便利ですね。ここ何年かずっと愛用しています。持ち歩く場合はだいたいこれです。\nそしてこれを使っているときに、たまにさくっとした Linux 環境が欲しくなることがあり、その目的でこれまでは crouton で chroot した xubuntu が動くようにしていたのですが、ハードウェアの制御（特にグラフィック）まわりでどうも不都合なシーンがちらほら出てくるようになりました。\nそんなわけで、とりあえず別のものを、ということで、chrx を使って GalliumOS をデュアルブートな感じで動かすことにしました。\n前提 RW_LEGACY をカスタムの SeaBIOS で書き換え済み デベロッパモードに変更済み 手順 インストールして初期設定をしていきます。\nパーティション分割 Ctrl + Alt + T で crosh を立ち上げて、シェルを起動します。\n\u0026gt; shell まずはパーティションを作ります。この辺も chrx におまかせです。以下を実行して、指示に従って数字を入力します。今回は 16 GB にしました。\ncd ; curl -Os https://chrx.org/go \u0026amp;\u0026amp; sh go 再起動しろと言われるので再起動すると、自動修復が始まります。左上のカウントが 00:00 になっても何も起きなくて怖いですが、そのまま気にせずに数分放置しておくと、勝手に起動してきます。\n起動してきたら、ChromeOS が完全に初期化されている状態なので、初期設定をして、ネットワークにつなげます。\nインストール パーティションが切れたら、またシェルを起動して、GalliumOS をインストールします。\ncd ; curl -Os https://chrx.org/go \u0026amp;\u0026amp; sh go -d galliumos -H c302ca -U kuro -p steam -p chrome -p admin-misc -L ja_JP.UTF-8 引数で渡すオプションは、-h で出るヘルプを見ながら適当にします。-H が設定するホスト名、-U が作成するユーザ名です。\n試した感じだと、タイムゾーン（-Z）の指定はうまく機能しなかったので、起動後に変更することにして、ここでは省きました。\nで、数十分で完了するので、指示に従って再起動し、通常 Ctrl + D で起動するところで Ctrl + L して、GalliumOS を起動させ、ログインします。\nユーザ名は -U で指定したもので、初期パスワードはユーザ名と同一です。\n日本語関連パッケージの導入 起動したら、言語周りを整えます。インストール時に -L で ja_JP.UTF-8 を指定しても、日本語環境で必要なすべてのパッケージが導入されるわけではないようです。\nで、Xfce のまま GUI から不足パッケージを突っ込もうとしたところ、固まって死んでしまいました。いちど multi-user.target に切り替えて作業するほうが良さそうです。\nそんなわけで、ターミナルを起動して、\nsudo systemctl isolate multi-user.target して、画面左上でカーソルが点滅したら、Alt + F1（F1 は ESC の右隣のキー）で仮想コンソールを切り替えてログインして、パッケージを入れて再起動します。\nsudo apt update sudo apt install $(check-language-support -l ja) sudo reboot 日本語入力の設定 起動後、日本語入力ができるようにします。\n左下のメニュ（Whisler Menu）の [設定] \u0026gt; [言語サポート] で、[キーボード入力に使う IM システム] が fcitx になっていることを確認します 右下の通知エリアのキーボードアイコンの右クリックメニュから [設定] を開き、Mozc だけを残して、それ以外をすべて - ボタンで削除します これで、\nキーボードレイアウトが正しい キーボード左上の [かな \u0026lt;-\u0026gt; 英数] キーで日本語入力と直接入力を切り替えられる な状態になります。im-config とか fcitx-configtool でも設定できる気がします。たぶん。\nパスワードの変更 キーボードレイアウトが正しくなったので、ターミナルで passwd してパスワードを変更します。\nタイムゾーンの設定 UTC のままなので、JST にします。\nメニュの [設定] \u0026gt; [時刻と日付の設定] で、[Unlock] します [タイムゾーン] を Asia/Tokyo にして、[Lock] します timedatectl でも大丈夫です。反映は再起動後です。\nオーディオの修正 GalliumOS の Hardware Compatibility でも記載されているとおり、C302（というか Skylake 系全般）は内蔵オーディオが正常に動作しないようです。実際、スピーカから何らかの音を再生させると、バキバキでひどい状態です。\nUSB や Bluetooth 接続のオーディオであれば正常に動作するようですが、GitHub の issue #379 で無理やりがんばる方法が出ていたのでそれをやっておきます。\nGoogle Drive 上にアップロードされた以下をダウンロードして、\nlinux-image-4.16.18-galliumos5-pre1_amd64.deb https://drive.google.com/open?id=1HZRq47yb51U70uyai-EjsD4qRcCuz6dB galliumos-skylake_3.0+dev3_supports_GaOS2.1_all.deb https://drive.google.com/open?id=15w2yzd8pTA7JasOJuOS0wucVzQE_QivK ターミナルでこれらを使ってごにょごにょします。\nsudo dpkg -i linux-image-4.16.18-galliumos5-pre1_amd64.deb sudo dpkg -x galliumos-skylake_3.0+dev3_supports_GaOS2.1_all.deb / sudo skylake-audio-helper get-topology sudo rm /lib/firmware/9d70-CORE-COREBOOT-0-tplg.bin sudo rm /lib/firmware/9d70-COREv4-COREBOOT-0-tplg.bin sudo rm /lib/firmware/9d70-INTEL-SCRDMAX-0-tplg.bin sudo skylake-audio-helper reset sudo reboot reset のときにスピーカがポンッてなるのでちょっとびっくりしました。再起動後は音声が正常に再生できます。\n","date":"2020-03-15T16:24:03Z","image":"/archives/2939/img/galliumos-3.0-architectural-scaled.jpg","permalink":"/archives/2939/","title":"ASUS Chromebook Flip C302CA を chrx で GalliumOS とのデュアルブートにする"},{"content":"昨今の情勢を受けて、家庭内の在宅勤務者の割合が 100 % になったので、在宅勤務中の QoL 的なのを上げようとしています。\nで、前から購入を計画していたデロンギの全自動コーヒーメーカを買いました。\nたいへんよいものでした。モノは デロンギのマグニフィカ S カプチーノスマート（ECAM23260SBN）です。\nコーヒー界隈はぜんぜん詳しくないのでよくわかりませんが、買ってからはもっぱら カフェ・ジャポーネ と名付けられたいわゆるレギュラーコーヒーを淹れて飲んでいます。\n朝起きてすぐとか食後とか、あ、コーヒー飲も、と思ったらすぐカップを置いてピッとすればギュイーンガガガガって動いてできあがるので、コーヒーを飲むまでのコストがめちゃくちゃ下がりました。\n味はしっかりおいしいです。知ってた。\nそしてこの子はミルクをどうにかする機能も付いているので、カプチーノも作れます。謎の技術により泡がめっちゃ甘くふわふわになるみたいです。\nみたいですというか、実際、なりました。すごく甘い。これホントにただの牛乳？ みたいな。ただの牛乳です。\nそしてふわふわです。カップが透明でないので写真では何も伝わりませんが。\nてきとうにシナモンなどをぱらぱらすると、よりオシャレさが増します。\n豆はまだ手探りです。KALDI のとか、成城石井のとか、海外の謎のヤツとか、いろいろ試そうとしています。\nお手入れがちょっと手間感があるのと、あと動作音がけっこう大きいのがマイナスですが、コーヒーがおいしいことはしあわせなことなので、全体的には圧倒的にプラスです。よいものです。\nしかし写真の被写界深度が浅すぎるので、もっと考えて撮るべきでした。おつかれさまでした。\n以上、よろしくお願いします。\n","date":"2020-03-07T07:55:12Z","image":"/archives/2919/img/DSC06342.jpg","permalink":"/archives/2919/","title":"全自動在宅勤務めっちゃはかどり機"},{"content":"EdgeX Foundry 関連エントリ 冬休みの自由研究： EdgeX Foundry (1) 概要 冬休みの自由研究： EdgeX Foundry (2) 導入とデータの確認 冬休みの自由研究： EdgeX Foundry (3) エクスポートサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (4) MQTT デバイスサービスの追加 冬休みの自由研究： EdgeX Foundry (5) ルールエンジンによる自動制御 冬休みの自由研究： EdgeX Foundry (6) アプリケーションサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (7) Kubernetes 上で Fuji リリースを動かす EdgeX Foundry ハンズオンラボガイド公開 EdgeX Foundry： Geneva から Hanoi へ EdgeX Foundry と Kubernetes これまでの EdgeX Foundry 関連エントリでは、一貫してその動作を Docker Compose に任せていました。公式でも Docker Compose や Snaps を利用して動作させる手順が紹介されています。\nが、最近よく Kubernetes（や OpenShift）を触っていることもあり、コンテナなら Kubernetes でも動かせるよね！ という気持ちになったので、やってみました。\nなお、現段階では、Kubernetes 上で動作させるためのマニフェストファイルは、公式には用意されていません。また、そもそも EdgeX Foundry は HA 構成を明確にはサポートしておらず、実装上も考慮されていないようです。つまり、仮に Kubernetes で動作させられたとしても、レプリカを増やしてロードバランスするような状態での正常な動作は何の保証もないことになります。\nというわけで、現状ではあくまで実験程度に捉えておくのがよいと思います。\n今回の目標 後述しますが、EdgeX Foundry の Kubernetes 上での動作は、過去にすでに試みられているので、今回はそれらの先行実装で解決されていない以下の 2 点を解消できる実装を目標としました。\n現時点での Fuji リリースを動作させられること マルチノード構成の Kubernetes で動かせること 幸いなことに、自宅の vSphere 環境では Cluster API を使ってマルチノードの Kubernetes 環境がすぐに作れるようになっている ので、この環境を使います。\nできあがったマニフェスト 紆余曲折を経てひとまず動くものができたので、リポジトリ に置いてあります。\n以下、使い方を簡単に紹介します。リポジトリはあらかじめクローンしておきます。\ngit clone https://github.com/kurokobo/edgex-on-kubernetes.git cd edgex-on-kubernetes Persistent Volume の用意 PV を 4 つ使うので、PV の実体となるものを用意します。今回は QNAP を NFS ストレージとして使いました。\nエクスポートされた NFS 領域を適当な作業用の Linux 端末からマウントして、PV の実体となるサブディレクトリを作っておきます。\nmount -t nfs 192.168.0.200:/k8s /mnt/k8s for i in $(seq -f %03g 100); do sudo mkdir /mnt/k8s/pv$i; done umount /mnt/k8s 続けて、これらの NFS 領域を Kubernetes の PersistentVolume として定義します。マニフェストの中身はこんな感じです。\n$ cat persistentvolumes/nfs/pv001-persistentvolume.yaml apiVersion: v1 kind: PersistentVolume metadata: name: pv001 spec: capacity: storage: 512Mi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Recycle nfs: server: 192.168.0.200 path: /k8s/pv001 当然ですが、これはぼくの環境に合わせたものなので、それ以外の環境で動かす場合は適宜修正は必要です。今回は nfs で書いていますが、hostPath でも gcePersistentDisk でも好きなようにしてください。\n逆にいえば、Deployment ではボリュームを PVC で割り当てているので、環境に合わせて編集するのは PV だけでどうにかなる気がします。\nマニフェストができたら、kubectl create します。\n$ ls persistentvolumes/nfs/*.yaml | xargs -n 1 kubectl create -f persistentvolume/pv001 created persistentvolume/pv002 created persistentvolume/pv003 created persistentvolume/pv004 created $ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv001 512Mi RWX Recycle Available 22s pv002 128Mi RWX Recycle Available 22s pv003 128Mi RWX Recycle Available 22s pv004 128Mi RWX Recycle Available 22s Persistent Volume Claim の用意 PV ができたら、PVC を作ります。\n$ ls persistentvolumeclaims/*.yaml | xargs -n 1 kubectl create -f persistentvolumeclaim/consul-config created persistentvolumeclaim/consul-data created persistentvolumeclaim/db-data created persistentvolumeclaim/log-data created $ kubectl get pv,pvc NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE persistentvolume/pv001 512Mi RWX Recycle Bound default/db-data 11m persistentvolume/pv002 128Mi RWX Recycle Bound default/consul-data 11m persistentvolume/pv003 128Mi RWX Recycle Bound default/log-data 11m persistentvolume/pv004 128Mi RWX Recycle Bound default/consul-config 11m NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE persistentvolumeclaim/consul-config Bound pv004 128Mi RWX 8s persistentvolumeclaim/consul-data Bound pv002 128Mi RWX 8s persistentvolumeclaim/db-data Bound pv001 512Mi RWX 8s persistentvolumeclaim/log-data Bound pv003 128Mi RWX 7s PV の STATUS が Bound になって、PVC の VOLUME で PV が対応付けられていれば大丈夫です。\nService の作成 続けて SVC を作ります。マニフェストファイル内で、外部から触りそうなヤツだけ type: LoadBalancer を書いて外部 IP アドレスを持たせています。\n$ ls services/*.yaml | xargs -n 1 kubectl create -f service/edgex-app-service-configurable-rules created service/edgex-core-command created service/edgex-core-consul created service/edgex-core-data created service/edgex-core-metadata created service/edgex-device-virtual created service/edgex-mongo created service/edgex-support-logging created service/edgex-support-notifications created service/edgex-support-rulesengine created service/edgex-support-scheduler created service/edgex-sys-mgmt-agent created service/edgex-ui-go created $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE edgex-app-service-configurable-rules ClusterIP 100.67.112.145 \u0026lt;none\u0026gt; 48100/TCP 8s edgex-core-command LoadBalancer 100.69.10.125 192.168.0.50 48082:31022/TCP 8s edgex-core-consul LoadBalancer 100.71.56.117 192.168.0.51 8400:30675/TCP,8500:32711/TCP 8s edgex-core-data LoadBalancer 100.70.131.230 192.168.0.52 48080:31879/TCP,5563:31195/TCP 8s edgex-core-metadata LoadBalancer 100.68.152.47 192.168.0.53 48081:30265/TCP 8s edgex-device-virtual ClusterIP 100.70.115.198 \u0026lt;none\u0026gt; 49990/TCP 8s edgex-mongo LoadBalancer 100.69.209.129 192.168.0.54 27017:30701/TCP 7s edgex-support-logging ClusterIP 100.65.29.76 \u0026lt;none\u0026gt; 48061/TCP 7s edgex-support-notifications ClusterIP 100.65.127.176 \u0026lt;none\u0026gt; 48060/TCP 7s edgex-support-rulesengine ClusterIP 100.64.125.224 \u0026lt;none\u0026gt; 48075/TCP 7s edgex-support-scheduler ClusterIP 100.70.168.47 \u0026lt;none\u0026gt; 48085/TCP 7s edgex-sys-mgmt-agent LoadBalancer 100.68.113.150 192.168.0.55 48090:32466/TCP 7s edgex-ui-go LoadBalancer 100.64.153.22 192.168.0.56 4000:32252/TCP 7s kubernetes ClusterIP 100.64.0.1 \u0026lt;none\u0026gt; 443/TCP 14m 外部からの触りっぷりをもうちょっときれいにしたい場合は、Ingress でも作るとよいと思います。\nDeployment の作成と Consul の初期化 ここまでできたら、あとは順次マイクロサービスを起動していきます。\n最初に、edgex-files を起動させて、PV 内にファイルを配置します。その後、Consul（edgex-core-consul）を起動させます。\n$ kubectl create -f pods/edgex-files-pod.yaml pod/edgex-files created $ while [[ $(kubectl get pods edgex-files -o \u0026#34;jsonpath={.status.phase}\u0026#34;) != \u0026#34;Succeeded\u0026#34; ]]; do sleep 1; done $ kubectl create -f deployments/edgex-core-consul-deployment.yaml deployment.apps/edgex-core-consul created $ kubectl wait --for=condition=available --timeout=60s deployment edgex-core-consul deployment.apps/edgex-core-consul condition met Consul が起動したら、edgex-core-config-seed を動作させて、Consul に初期設定値を登録させます。\n$ kubectl create -f pods/edgex-core-config-seed-pod.yaml pod/edgex-core-config-seed created $ while [[ $(kubectl get pods edgex-core-config-seed -o \u0026#34;jsonpath={.status.phase}\u0026#34;) != \u0026#34;Succeeded\u0026#34; ]]; do sleep 1; done edgex-files と edgex-core-config-seed は、それぞれ edgex-files はファイルのコピーが終わったら、edgex-core-config-seed は Consul に値を登録し終わったら不要になります。そのため、ここではこれらは Deployment リソースとしてではなく Pod リソースとして定義しています。\n残りの Deployment の作成 残りのマイクロサービスを起動していきます。\n$ kubectl create -f deployments/edgex-mongo-deployment.yaml deployment.apps/edgex-mongo created $ kubectl wait --for=condition=available --timeout=60s deployment edgex-mongo deployment.apps/edgex-mongo condition met $ kubectl create -f deployments/edgex-support-logging-deployment.yaml deployment.apps/edgex-support-logging created $ kubectl wait --for=condition=available --timeout=60s deployment edgex-support-logging deployment.apps/edgex-support-logging condition met $ kubectl create -f deployments/edgex-support-notifications-deployment.yaml deployment.apps/edgex-support-notifications created $ kubectl wait --for=condition=available --timeout=60s deployment edgex-support-notifications deployment.apps/edgex-support-notifications condition met $ kubectl create -f deployments/edgex-core-metadata-deployment.yaml deployment.apps/edgex-core-metadata created $ kubectl wait --for=condition=available --timeout=60s deployment edgex-core-metadata deployment.apps/edgex-core-metadata condition met $ kubectl create -f deployments/edgex-core-data-deployment.yaml deployment.apps/edgex-core-data created $ kubectl wait --for=condition=available --timeout=60s deployment edgex-core-data deployment.apps/edgex-core-data condition met $ kubectl create -f deployments/edgex-core-command-deployment.yaml deployment.apps/edgex-core-command created $ kubectl wait --for=condition=available --timeout=60s deployment edgex-core-command deployment.apps/edgex-core-command condition met $ kubectl create -f deployments/edgex-support-scheduler-deployment.yaml deployment.apps/edgex-support-scheduler created $ kubectl wait --for=condition=available --timeout=60s deployment edgex-support-scheduler deployment.apps/edgex-support-scheduler condition met $ kubectl create -f deployments/edgex-support-rulesengine-deployment.yaml deployment.apps/edgex-support-rulesengine created $ kubectl wait --for=condition=available --timeout=60s deployment edgex-support-rulesengine deployment.apps/edgex-support-rulesengine condition met $ kubectl create -f deployments/edgex-app-service-configurable-rules-deployment.yaml deployment.apps/edgex-app-service-configurable-rules created $ kubectl wait --for=condition=available --timeout=60s deployment edgex-app-service-configurable-rules deployment.apps/edgex-app-service-configurable-rules condition met $ kubectl create -f deployments/edgex-device-virtual-deployment.yaml deployment.apps/edgex-device-virtual created $ kubectl wait --for=condition=available --timeout=60s deployment edgex-device-virtual deployment.apps/edgex-device-virtual condition met $ kubectl create -f deployments/edgex-sys-mgmt-agent-deployment.yaml deployment.apps/edgex-sys-mgmt-agent created $ kubectl wait --for=condition=available --timeout=60s deployment edgex-sys-mgmt-agent deployment.apps/edgex-sys-mgmt-agent condition met $ kubectl create -f deployments/edgex-ui-go-deployment.yaml deployment.apps/edgex-ui-go created $ kubectl wait --for=condition=available --timeout=60s deployment edgex-ui-go deployment.apps/edgex-ui-go condition met 完成 完成です。\n$ kubectl get all,pv,pvc NAME READY STATUS RESTARTS AGE pod/edgex-app-service-configurable-rules-6c794d754d-xj4v4 1/1 Running 0 92s pod/edgex-core-command-cc8465b6f-kgwhm 1/1 Running 0 98s pod/edgex-core-config-seed 0/1 Completed 0 2m13s pod/edgex-core-consul-5b8cdbbfc8-g7j6t 1/1 Running 0 2m26s pod/edgex-core-data-c7775c56b-t8rdf 1/1 Running 0 100s pod/edgex-core-metadata-66f86d86b-mfqfg 1/1 Running 0 102s pod/edgex-device-virtual-597f9d77f-vqlln 1/1 Running 0 90s pod/edgex-files 0/1 Completed 0 6m13s pod/edgex-mongo-99c7d8957-fsz6c 1/1 Running 0 110s pod/edgex-support-logging-8566649bf8-dj8fw 1/1 Running 0 107s pod/edgex-support-notifications-586cf544f-n8md7 1/1 Running 0 104s pod/edgex-support-rulesengine-6cfc49f89b-xn8fc 1/1 Running 0 94s pod/edgex-support-scheduler-7658f985bb-9bmz2 1/1 Running 0 96s pod/edgex-sys-mgmt-agent-5dd56c65bb-tnqth 1/1 Running 0 88s pod/edgex-ui-go-54668dcc94-mdx2d 1/1 Running 0 86s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/edgex-app-service-configurable-rules ClusterIP 100.67.11.126 \u0026lt;none\u0026gt; 48100/TCP 7m33s service/edgex-core-command LoadBalancer 100.71.84.78 192.168.0.50 48082:32108/TCP 7m33s service/edgex-core-consul LoadBalancer 100.66.136.131 192.168.0.51 8400:31442/TCP,8500:31806/TCP 7m32s service/edgex-core-data LoadBalancer 100.64.210.255 192.168.0.52 48080:31808/TCP,5563:30664/TCP 7m32s service/edgex-core-metadata LoadBalancer 100.67.207.51 192.168.0.53 48081:32379/TCP 7m32s service/edgex-device-virtual ClusterIP 100.69.130.2 \u0026lt;none\u0026gt; 49990/TCP 7m32s service/edgex-mongo LoadBalancer 100.66.8.30 192.168.0.54 27017:31973/TCP 7m32s service/edgex-support-logging ClusterIP 100.68.171.73 \u0026lt;none\u0026gt; 48061/TCP 7m32s service/edgex-support-notifications ClusterIP 100.67.242.204 \u0026lt;none\u0026gt; 48060/TCP 7m32s service/edgex-support-rulesengine ClusterIP 100.67.203.199 \u0026lt;none\u0026gt; 48075/TCP 7m32s service/edgex-support-scheduler ClusterIP 100.65.53.193 \u0026lt;none\u0026gt; 48085/TCP 7m31s service/edgex-sys-mgmt-agent LoadBalancer 100.65.122.248 192.168.0.55 48090:31476/TCP 7m31s service/edgex-ui-go LoadBalancer 100.70.107.81 192.168.0.56 4000:30852/TCP 7m31s service/kubernetes ClusterIP 100.64.0.1 \u0026lt;none\u0026gt; 443/TCP 47m NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/edgex-app-service-configurable-rules 1/1 1 1 92s deployment.apps/edgex-core-command 1/1 1 1 98s deployment.apps/edgex-core-consul 1/1 1 1 2m26s deployment.apps/edgex-core-data 1/1 1 1 100s deployment.apps/edgex-core-metadata 1/1 1 1 102s deployment.apps/edgex-device-virtual 1/1 1 1 90s deployment.apps/edgex-mongo 1/1 1 1 110s deployment.apps/edgex-support-logging 1/1 1 1 107s deployment.apps/edgex-support-notifications 1/1 1 1 104s deployment.apps/edgex-support-rulesengine 1/1 1 1 94s deployment.apps/edgex-support-scheduler 1/1 1 1 96s deployment.apps/edgex-sys-mgmt-agent 1/1 1 1 88s deployment.apps/edgex-ui-go 1/1 1 1 86s NAME DESIRED CURRENT READY AGE replicaset.apps/edgex-app-service-configurable-rules-6c794d754d 1 1 1 92s replicaset.apps/edgex-core-command-cc8465b6f 1 1 1 98s replicaset.apps/edgex-core-consul-5b8cdbbfc8 1 1 1 2m26s replicaset.apps/edgex-core-data-c7775c56b 1 1 1 100s replicaset.apps/edgex-core-metadata-66f86d86b 1 1 1 102s replicaset.apps/edgex-device-virtual-597f9d77f 1 1 1 90s replicaset.apps/edgex-mongo-99c7d8957 1 1 1 110s replicaset.apps/edgex-support-logging-8566649bf8 1 1 1 107s replicaset.apps/edgex-support-notifications-586cf544f 1 1 1 104s replicaset.apps/edgex-support-rulesengine-6cfc49f89b 1 1 1 94s replicaset.apps/edgex-support-scheduler-7658f985bb 1 1 1 96s replicaset.apps/edgex-sys-mgmt-agent-5dd56c65bb 1 1 1 88s replicaset.apps/edgex-ui-go-54668dcc94 1 1 1 86s NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE persistentvolume/pv001 512Mi RWX Recycle Bound default/db-data 8m11s persistentvolume/pv002 128Mi RWX Recycle Bound default/consul-config 8m11s persistentvolume/pv003 128Mi RWX Recycle Bound default/consul-data 8m10s persistentvolume/pv004 128Mi RWX Recycle Bound default/log-data 8m10s NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE persistentvolumeclaim/consul-config Bound pv002 128Mi RWX 8m4s persistentvolumeclaim/consul-data Bound pv003 128Mi RWX 8m4s persistentvolumeclaim/db-data Bound pv001 512Mi RWX 8m4s persistentvolumeclaim/log-data Bound pv004 128Mi RWX 8m4s 動作確認 標準で edgex-device-virtual が動いているので、edgex-core-data のログなどを除けば値が取り込まれていることが観察できます。\n$ kubectl logs edgex-core-data-c7775c56b-t8rdf --tail 4 level=INFO ts=2020-03-01T07:41:47.454970177Z app=edgex-core-data source=event.go:240 msg=\u0026#34;Putting event on message queue\u0026#34; level=INFO ts=2020-03-01T07:41:47.455410868Z app=edgex-core-data source=event.go:258 msg=\u0026#34;Event Published on message queue. Topic: events, Correlation-id: 4d142e84-f3b2-49cd-a1d4-8be7dc7e5d36 \u0026#34; level=INFO ts=2020-03-01T07:41:47.47515949Z app=edgex-core-data source=event.go:240 msg=\u0026#34;Putting event on message queue\u0026#34; level=INFO ts=2020-03-01T07:41:47.47523845Z app=edgex-core-data source=event.go:258 msg=\u0026#34;Event Published on message queue. Topic: events, Correlation-id: 6e6f7f2a-da7f-4fc7-ac1f-85e5918c31ed \u0026#34; 適当に MQTT トピックにエクスポートさせると、実際に値が届いていることが確認できます。エクスポート先の MQTT ブローカは、マニフェストファイルで環境変数として定義しているので、適宜修正してください。\n$ docker run -d --rm --name broker -p 1883:1883 eclipse-mosquitto 598878ed584699c61b4ccb95cdaeb53cc70b3a56793b53c83db1511483dc037a $ kubectl create -f deployments/edgex-app-service-configurable-mqtt-export-deployment.yaml deployment.apps/edgex-app-service-configurable-mqtt-export created $ kubectl wait --for=condition=available --timeout=60s deployment edgex-app-service-configurable-mqtt-export deployment.apps/edgex-app-service-configurable-mqtt-export condition met $ docker run --init --rm -it efrecon/mqtt-client sub -h 192.168.0.100 -t \u0026#34;#\u0026#34; -v edgex-handson-topic {\u0026#34;id\u0026#34;:\u0026#34;5a79e954-6c3f-4635-9e60-d93379a2a7ec\u0026#34;,\u0026#34;device\u0026#34;:\u0026#34;Random-UnsignedInteger-Device\u0026#34;,\u0026#34;origin\u0026#34;:1583049398041588772,\u0026#34;readings\u0026#34;:[{\u0026#34;id\u0026#34;:\u0026#34;529f5003-47b6-45ab-a743-7b4a21d937f4\u0026#34;,\u0026#34;origin\u0026#34;:1583049398026423142,\u0026#34;device\u0026#34;:\u0026#34;Random-UnsignedInteger-Device\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;Uint8\u0026#34;,\u0026#34;value\u0026#34;:\u0026#34;80\u0026#34;}]} edgex-handson-topic {\u0026#34;id\u0026#34;:\u0026#34;2cd0b56e-28c8-4035-9351-75028b37afef\u0026#34;,\u0026#34;device\u0026#34;:\u0026#34;Random-UnsignedInteger-Device\u0026#34;,\u0026#34;origin\u0026#34;:1583049398427444993,\u0026#34;readings\u0026#34;:[{\u0026#34;id\u0026#34;:\u0026#34;0a041b11-76f9-426c-9098-75affe58ea89\u0026#34;,\u0026#34;origin\u0026#34;:1583049398412848451,\u0026#34;device\u0026#34;:\u0026#34;Random-UnsignedInteger-Device\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;Uint64\u0026#34;,\u0026#34;value\u0026#34;:\u0026#34;2542183823530459689\u0026#34;}]} edgex-handson-topic {\u0026#34;id\u0026#34;:\u0026#34;00aaffb1-27ca-43ff-8150-ace2eefe2a32\u0026#34;,\u0026#34;device\u0026#34;:\u0026#34;Random-Boolean-Device\u0026#34;,\u0026#34;origin\u0026#34;:1583049398815297954,\u0026#34;readings\u0026#34;:[{\u0026#34;id\u0026#34;:\u0026#34;7ecc8518-ae56-4c78-8527-dd3442b14c6d\u0026#34;,\u0026#34;origin\u0026#34;:1583049398800419572,\u0026#34;device\u0026#34;:\u0026#34;Random-Boolean-Device\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;Bool\u0026#34;,\u0026#34;value\u0026#34;:\u0026#34;false\u0026#34;}]} ... 残されている問題 そんなわけで、とりあえずは動いていますが、\n起動・停止が面倒 マイクロサービス間に依存関係があり、起動順序に配慮が必要（依存関係自体は Docker Compose でも定義されているし起動順序も公式ドキュメントに書いてあるので無視できない） Kubernetes では起動順序を制御しにくい（実装が検討されているっぽい のでそれに期待） 現状では、コマンド群をシェルスクリプトにするか、InitContianers などでがんばるのが精々。Kustomize とか Helm でも細かい順序制御はしんどい気がする 設定ファイルの流し込みがしにくい configuration.toml などをコンテナに渡しにくい PV を新しく作って InitContainers で git clone するなどの工夫が必要 ヘルスチェックをしていない いわゆる Liveness Probe、Rediness Probe の追加をサボった edgex-sys-mgmt-agent が部分的に動かないかも（未確認） /var/run/docker.sock を消したが影響を確認していない あたりに本気で使うにはハードルがありそうです。将来的には公式に参考実装やベストプラクティスが出てくるといいですね。\nマニフェストの作成過程 いきなりできあがったモノを紹介しましたが、作る過程をざっくり書いておきます。\nとりあえず Kompose で変換する Docker Compose ファイルを Kubernetes 用のマニフェストに変換できる Kompose というものがあります。おもむろにこれに突っ込めば完成！\n$ kompose convert -f docker-compose.yml INFO Network edgex-network is detected at Source, shall be converted to equivalent NetworkPolicy at Destination ... INFO Kubernetes file \u0026#34;app-service-rules-service.yaml\u0026#34; created ... INFO Kubernetes file \u0026#34;edgex-network-networkpolicy.yaml\u0026#34; created となるはずもなく、このままだと全然動きません。この作業で、\nServices（*-service.yaml） Deployments（*-deployment.yaml） Persistent Volyme Claims（*-persistentvolumeclaim.yaml） Network Policy（*-networkpolicy.yaml） の 4 種類のファイルができあがっています。\nこのままだと動かないとは言いつつ、ベースとしては充分使えるので、これを編集していく形で実装を考えます。\nアーキテクチャを考える この段階で、テストだからがばがばでよしとすることにして、Network Policy のマニフェストは消しました。\n残りのマニフェストで定義された構成を、以下のように考えて組み替えていきます。\n共有ボリュームの持たせ方 Docker Compose ファイルだと、ボリュームが無駄に広い範囲で共有されすぎている気がする 最新の Nightly Build リリースの Docker Compose ファイルをみると、最小限に絞られていそうだ Nightly Build 版での共有っぷりを参考に、今回も共有範囲は最小限に絞ることにする 共有ボリュームへのファイルの配置の仕方 edgex-files のコンテナイメージ内に共有ボリューム上に置くべきファイルが保存されている Docker Compose と同じようにマウントしてしまうと、既存のファイルがオーバライドされて見えなくなる 別のパスにマウントして、command と args で必要なファイルをコピーさせるようにする 一度だけ動けばよいので、restartPolicy を Never にした Pod にする 設定の配り方 先行実装では edgex-config-seed を消し去っていた（ローカルの設定ファイルを使わせていた）例がある とはいえ EdgeX Foundry は Consul ありきでできているフシもあるので、できれば Docker Compose のときと同じようにしたい 一度だけ動けばよいので、restartPolicy を Never にした Pod にする Docker ならではの実装の排除 Portainer は Docker 前提なので消すことにする /var/run/docker.sock のマウントも Docker 前提なので消すことにする PV の作り方 Deployment に直接 PV を持たせるのではなく、お作法通り PVC を使ってマウントさせるようにする マニフェストの整形 方針が決まったら、Kompose で変換されたマニフェストファイルを実際にきれいにしていきます。\nServices metadata を削除、または修正 ほかのマイクロサービスから正しく名前解決できるように name を各マイクロサービスのホスト名に修正 selector を Deployment に合わせて修正 外部からアクセスしそうなサービスに type: LoadBalancer を追加 Deployments API バージョンを修正、併せて必須パラメータ（selector）追加 metadata を削除、または修正 Deployment と Pod の name を各マイクロサービスのホスト名に統一 不要なボリュームの削除 edgex-core-config-seed と edgex-files の削除 Pods edgex-core-config-seed と edgex-files を新規に追加 restartPolicy やボリュームの変更、command と args の追加 Persistent Volume Claims metadata を削除、または修正 accessModes は ReadWriteMany に変更 サイズを変更（値は このとき の実使用量を基に決定） Persistent Volumes 新規に追加 こまごまトラブルシュート で、だいたいできたと思ったら、一部のマイクロサービスで、REST エンドポイントとして必要な HTTP サーバが起動しない状態になりました。以下のような具合で、起動しようとした直後に停止してしまっています。\n$ kubectl logs edgex-support-logging-66fc4f6c98-28bk4 ... level=INFO ts=2020-02-25T12:47:50.622662811Z app=edgex-support-logging source=httpserver.go:72 msg=\u0026#34;Web server starting (edgex-support-logging:48061)\u0026#34; level=INFO ts=2020-02-25T12:47:50.624106767Z app=edgex-support-logging source=httpserver.go:80 msg=\u0026#34;Web server stopped\u0026#34; ... いろいろ調べたら、以下のような状態のようでした。\nedgex-go リポジトリで管理されているマイクロサービスで起きる 具体的には内部で go-mod-bootstrap の httpserver を使っていると起きる HTTP サーバは、マイクロサービスの起動時に \u0026lt;ホスト名\u0026gt;:\u0026lt;ポート番号\u0026gt; で待ち受けを指示される 上記のログの例だと edgex-support-logging:48061 待ち受けのホスト名とポート番号は Consul から受け取る HTTP サーバは、指示された \u0026lt;ホスト名\u0026gt; の名前解決を試みる コンテナ内の /etc/hosts を無視して、Kubernetes による名前解決が行われる 名前解決したいホスト名が Service の名前と一致しているため、Kubernetes の ClusterIP に解決される Pod 内のプロセスからは ClusterIP は利用できない（Pod の IP アドレスしか認識されない）ため、待ち受けできずに死ぬ Consul 上のホスト名を書き換えるのも、Pod と Service でホスト名を別にするのも、副作用がわからないのでできれば避けたいところです。そもそも Kubernetes の仕様上、コンテナ内の /etc/hosts には Pod の IP アドレスと Pod のホスト名が正しく書いてあるわけですから、これを参照してくれさえすればよいだけの話です。\nが、Go 言語のデフォルトの名前解決の仕組みでは、/etc/nsswitch.conf が存在しない（または存在していても中で files が指定されていない）場合に、/etc/hosts は無視されるようです。この挙動の修正は 1.15 のロードマップに乗っている ものの、要するに、現時点ではそういう仕様ということになります。\nそして、問題が起きているマイクロサービスのコンテナイメージは、scratch や alpine などがベースになっているので、/etc/nsswitch.conf が存在しません。\nというわけで、いろいろな仕様が重なった結果として、こういう問題が引き起こされています。がんばりましょう。\nこの解決にあたって、以下の 3 案を考えました。今回はいちばんマニフェストへの取り込みがラクな最初の案を採用しています。\n環境変数 GODEBUG に、値 netdns=cgo を与える 名前解決に Go 標準ではなく CGO の仕組みを使うことを強制するオプション CGO では /etc/nsswitch.conf が存在していなくても /etc/hosts が読み取られる /etc/nsswitch.conf（中身は hosts: files dns）をコンテナ内に配置する コンテナイメージを自製するか、PV や InitContainer などを使ってがんばる必要がある Consul の K/V ストアの /edgex/core/1.0/*/Service/Host を全部 0.0.0.0 にする Consul を起動させて、手で書き換えてから残りのサービスを起動するようにする 副作用が不明 先行実装 冒頭で書いた通り、EdgeX Foundry の Kubernetes 上での動作は、すでに過去に試みられています。現状で確認できているものを簡単に紹介します。\nEdgeX on Kubernetes EdgeX Foundry のブログの 2018 年のエントリで、当時のリリースを Kubernetes で動作させる例が紹介されています。\nRunning EdgeX Foundry on Kubernetes https://www.edgexfoundry.org/blog/2018/01/30/huawei-runs-edgex-foundry-kubernetes/ https://github.com/rohitsardesai83/edgex-on-kubernetes 2018 年当時なので、使われているイメージは古く、どうやら Barcelona ベースのようです。\nまた、共有ボリュームとして利用する PV が hostPath で定義されているので、シングルノード上での動作が前提になっているものと推測されます。\nedgex-kubernetes-support GitHub の edgexfoundry-holding 配下のリポジトリとして用意されています。比較的新しめです。\nedgex-kubernetes-support https://github.com/edgexfoundry-holding/edgex-kubernetes-support/ こちらは Edinburgh をベースにしたものと Nightly Build をベースにしたものがありました。Nightly Build とはいえ、半年以上前のそれなので、実際は Fuji 相当に近そうです。Helm チャートも用意されています。\nK8s だけでなく K3s も対象としている点はうれしいですが、これも PV が hostPath で定義されているので、シングルノードでの動作を前提としているようです。\nまとめ EdgeX Foundry の Fuji リリースを、マルチノード構成の Kubernetes 上で動作させるためのマニフェストファイルを作成して、動作を確認しました。\nKompose は便利ですが、やはりそれなりの規模の Docker Compose ファイルだと完全に無編集でそのまま使えるというわけにはいかないですね。よい経験になりました。\nEdgeX Foundry も Kubernetes も、本当に日々更新されていっているので、追従し続けるのは相当しんどそうです。このブログの EdgeX Foundry 関連エントリも、来月に予定されている次期バージョン（Geneva）のリリースですぐに時代遅れになるわけです。がんばりましょう。\nEdgeX Foundry 関連エントリ 冬休みの自由研究： EdgeX Foundry (1) 概要 冬休みの自由研究： EdgeX Foundry (2) 導入とデータの確認 冬休みの自由研究： EdgeX Foundry (3) エクスポートサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (4) MQTT デバイスサービスの追加 冬休みの自由研究： EdgeX Foundry (5) ルールエンジンによる自動制御 冬休みの自由研究： EdgeX Foundry (6) アプリケーションサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (7) Kubernetes 上で Fuji リリースを動かす EdgeX Foundry ハンズオンラボガイド公開 EdgeX Foundry： Geneva から Hanoi へ ","date":"2020-03-01T10:27:32Z","image":"/archives/2904/img/DSC06315.jpg","permalink":"/archives/2904/","title":"冬休みの自由研究： EdgeX Foundry (7) Kubernetes 上で Fuji リリースを動かす"},{"content":"マウスを買ったので写真を撮りました。というエントリです。\nおしごと用のマウスは、2012 年から Logicool の Anywhere Mouse（M905r）を愛用しています。数年おきにチャタリングが起きるようになっていましたが、都度交換してもらって、保証期間をだいぶ過ぎた今でも現役です。\n一方、自宅用のマウスは、気分でいろいろとっかえひっかえしており、とくにコレと決めたものはありませんでした。が、最近チャタリングが気になりだしたので、慣れたものに決めてしまおうと、おしごと用の M905r の後継である MX Anywhere 2S（MX1600s）を購入したわけです。\nこれです。\nかわいいですね。\n並べてみましょう。M905r はつるっとしていましたが、MX1600s ではマットな仕上げに変わっています。かっこうよいですね。\nまた、M905r は乾電池式でしたが、MX1600s は、USB で給電する充電池式です。電池の寿命を無視できるのはうれしいですね。何年でヘタるかはわかりませんが……。\nでも、実は不満もあるのです。\nM905r は、電池の蓋をあけたところに Unifying レシーバを格納できましたが、MX1600s ではこれができません。\nとはいえ、MX1600s は例の Logicool Flow に対応しているので、Unifying レシーバを差し替えて複数台で使いまわすようなこと自体が、もはや本来の想定された使い方ではないということなのでしょう。たぶん。\nしかし、それでも、それでもまだ不満はあるのです。\nM905r はスイッチのオンオフが信じられないくらいラクだったのです。なぜなら物理的にスイッチが大きくて操作しやすいからで、オフィス内をマウスとともにうろうろする前後で片手でスイッとできていました。\nMX1600s は、スイッチがほんとうに小さくて、爪が短いと操作しづらいレベルです。ぼくの使い方だとユーザビリティが著しく悪化したポイントです。\nまた、M905r に付属していたソフトケースが無くなったのも個人的には非常に痛いところです。おしごと用の M905r は、鞄に放り込んでの持ち運びを散々しているので、ソフトケースが大活躍だったのでした。\nというわけで、最新世代たる MX1600s は、モバイル用を謳った後継機ではあるものの、ぼくにとっては一部不満が残ってしまうものでした。\nでもなんだかんだいいながらも、マウスとして一番だいじな実際の使い心地は、相変わらず抜群に快適です。この辺の品質に妥協がないのは安心感があります。また、サイズ感もまったく変わっていないので、何の違和感もなく一瞬で馴染みました。\nソフトケースはいざとなったら M905r のを今後も使えばいいし、素直に Logicool Flow をセットアップすれば Unifying レシーバを持ち歩く必要もなくせるでしょう。スイッチの形状だけはどうしようもないですが、きっと慣れます。\n以上、おすすめマウスの紹介でした。\n今回、撮影ボックスを初めて使ってみました。ライティングはもう少し工夫したいですし、こういう写真のときの被写界深度の考え方はお勉強が必要です。\n","date":"2020-02-12T15:54:28Z","image":"/archives/2877/img/DSC06322.jpg","permalink":"/archives/2877/","title":"Logicool MX Anywhere 2S（MX1600s）購入"},{"content":"きっかけ 実験したいことが出てきてしまい、自宅で Kubernetes を触りたくなりました。\nこれまで Kubernetes を触る場合は Google Kubernetes Engine（GKE）ばかりを使っていたのですが、 今回実験したいのは IoT の世界でいうエッジ側の話なので、できればオンプレミス相当の Kuberentes クラスタが欲しいところです。\nそんなわけで、これ幸いと自宅の vSphere 環境で Cluster API を叩いてゲストクラスタを作ることにしました。Cluster API は、2019 年に VMware から発表された VMware Tanzu や Project Pacific の実装でも使われているそうで、そういう意味でも興味のあるところです。\nVMware Tanzu や Project Pacific は、エントリの本筋ではないので細かくは書きませんが、めっちゃ雑に書くと、vSphere と Kubernetes がイケてる感じにくっついたヤツです。\nCluster API とは Cluster API は、Kubernetes っぽいお作法で Kubernetes クラスタそれ自体を管理できる仕組みです。Kubernetes の SIG のプロジェクト として開発が進められています。\n最新バージョンは 2019 年 9 月にリリースされた v1alpha2 で、現在は v1alpha3 が開発中です。バージョン名を見てもわかる通り、現段階ではいわゆるアルファ版ですし、ドキュメントでも『プロトタイプだよ』『どんどん変わるからね』と記載されているので、ごりごりに使い込むのはまだちょっと待ったほうがよさそうですね。\nCluster API is still in a prototype stage while we get feedback on the API types themselves. All of the code here is to experiment with the API and demo its abilities, in order to drive more technical feedback to the API design. Because of this, all of the codebase is rapidly changing. https://github.com/kubernetes-sigs/cluster-api/blob/master/README.md\rKubernetes クラスタを構成するノードは、多くの場合は仮想マシンです。その仮想マシンは、パブリッククラウド上だったりオンプレミスの vSphere 上や OpenStack 上だったりで動いているわけですが、Cluster API では、そうしたプラットフォームごとに Provider なる実装が用意されており、環境差異を抽象化してくれます。これにより、異なる環境でも同一の操作感で Kubernetes クラスタを管理できます。\n今回は vSphere 環境上の Kubernetes クラスタの構成が目的なので、vSphere 用の Provider を使って作業します。\n構成要素と構成の流れ 最終的には、いわゆる Kubernetes らしく業務や開発で様々なアプリケーションを動作させることになる Kubernetes クラスタと、それらを管理するためだけの Kubernetes クラスタ、の大きく二種類の Kubernetes クラスタができあがります。\n前者がゲストクラスタ（ワークロードクラスタ）、後者がマネジメントクラスタなどと呼ばれるようです。Cluster API はこのうちのマネジメントクラスタに組み込まれており、この Cluster API によってゲストクラスタのライフサイクルを簡単に管理できるということです。\nもう少し具体的にいえば、例えば Kubernetes クラスタ自体は cluster リソースとして、あるいはそれを構成するノードの仮想マシンは machine リソースとして扱えるようになり、通常の pod リソースや deployment リソースと同じように、自分以外の Kubernetes クラスタの構成が管理できるということです。\n構築の観点では、つまりマネジメントクラスタができさえすれば Cluster API 環境はほぼ完成と言えるわけですが、実際にはマネジメントクラスタ自身もマネジメントクラスタで管理するため、手順はちょっと複雑です。\nマネジメントクラスタの作り方はいくつかあるようですが、今回は vSphere 用 Provider の Getting Started の通り、以下のような流れで構成を進めます。\n作業用端末（図中 Workstation）に Docker や kuberctl など必要なモノを揃えて、マニフェストファイルを生成する Docker 上に作業用の Kubernetes クラスタ（ブートストラップクラスタ）を作り、Cluster API を導入する 導入した Cluster API を使って、マニフェストファイルに従って本当のマネジメントクラスタを作る マネジメントクラスタに Cluster API 環境を移行（Pivoting）して、ブートストラップクラスタを消す 最終的なマネジメントクラスタを作るためにさらに別の Kubernetes クラスタ（ブートストラップクラスタ）が必要なあたりがわりとややこしいですが、そういうものみたいです。\nここまでできたら、Cluster API の本来の利用方法の通り、マニフェストファイルに従ってゲストクラスタを作ったり消したり拡張したり縮小したりできます。\n構築の準備 前述した流れの通り、マネジメントクラスタを構築するには、ブートストラップクラスタが動作できる環境が必要です。また、マネジメントクラスタが動作する vSphere 環境でも、少し準備が必要です。\n作業端末の整備 作業前提を整えます。作業用の端末は、ブートストラップクラスタの動作と、それを用いたマネジメントクラスタの構築ができる必要があり、このために、\nDocker clusterctl Kind kubectl が必要です。Windows でもおそらく動くとは思いますが、今回は作業用の Ubuntu 端末を別に用意して使っています。\nDocker は適当に入れます。今回の端末は Ubuntu 19.10 なので、19.04 用のバイナリを無理やり入れます。\nsudo apt update sudo apt install apt-transport-https ca-certificates curl software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository \u0026#34;deb [arch=amd64] https://download.docker.com/linux/ubuntu disco stable\u0026#34; sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io sudo usermod -aG docker ${USER} clusterctl と Kind、kubectl はバイナリをダウンロードするだけです。\ncurl -Lo ./clusterctl https://github.com/kubernetes-sigs/cluster-api/releases/download/v0.2.9/clusterctl-darwin-amd64 chmod +x ./clusterctl mv ./clusterctl ~/bin curl -Lo ./kind https://github.com/kubernetes-sigs/kind/releases/download/v0.6.1/kind-$(uname)-amd64 chmod +x ./kind mv ./kind ~/bin curl -Lo ./kubectl https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl chmod +x ./kubectl mv ./kubectl ~/bin が、clusterctl は v1alpha2 の時点ですでに DEPRECATED 扱いでした。\nしかしながら代替手段がよくわかっていないし、Cluster API のドキュメント 通りではなにやらうまく動かない（kubeconfig 用の secret ができあがらない）ので、v1alpha3 が出て情報が増えてきたらどうにかします。\nあと、Kind の最新リリースは現時点で 0.7.0 ですが、それを使うと後続の作業がうまく動かなかったので、ひとつ古い 0.6.1 を指定しています。\nvSphere 環境での OVA テンプレートの展開 vSphere 環境用の Provider を使って Kubernetes クラスタを作った場合、最終的にできあがる Kubernetes のノードは vSphere 環境上の仮想マシンです。\nこの仮想マシンの元になるテンプレートが用意されているので、これをあらかじめデプロイして、テンプレートに変換しておきます。\nUbuntu 版と CentOS 版がありますが、今回は Ubuntu 版の最新の 1.16.3 を使いました。\nUbuntu 18.04 版 CentOS 7 版 その他のイメージのリンクは リポジトリに一覧 されています。\nvSphere 環境でのフォルダとリソースプールの作成 マネジメントクラスタとゲストクラスタを入れるフォルダ（仮想マシンインベントリのヤツ）とリソースプールを作ります。入れ物としてただあればよいので、設定は適当で大丈夫です。\nマネジメントクラスタの構成 では、実際の構築を進めます。ブートストラップクラスタを作り、そこで Cluster API を動作させて、それを通じて最終的なマネジメントクラスタを作ります。\nはじめに、構成に必要な環境変数を、envvars.txt にまとめて定義します。内容はそれぞれの環境に依存するので書き換えが必要です。\n$ cat envvars.txt # vCenter config/credentials export VSPHERE_SERVER=\u0026#39;192.168.0.201\u0026#39; # (required) The vCenter server IP or FQDN export VSPHERE_USERNAME=\u0026#39;administrator@vsphere.local\u0026#39; # (required) The username used to access the remote vSphere endpoint export VSPHERE_PASSWORD=\u0026#39;my-secure-password\u0026#39; # (required) The password used to access the remote vSphere endpoint # vSphere deployment configs export VSPHERE_DATACENTER=\u0026#39;kuro-dc01\u0026#39; # (required) The vSphere datacenter to deploy the management cluster on export VSPHERE_DATASTORE=\u0026#39;nfs-ds01\u0026#39; # (required) The vSphere datastore to deploy the management cluster on export VSPHERE_NETWORK=\u0026#39;ext-vm\u0026#39; # (required) The VM network to deploy the management cluster on export VSPHERE_RESOURCE_POOL=\u0026#39;k8s\u0026#39; # (required) The vSphere resource pool for your VMs export VSPHERE_FOLDER=\u0026#39;k8s\u0026#39; # (optional) The VM folder for your VMs, defaults to the root vSphere folder if not set. export VSPHERE_TEMPLATE=\u0026#39;template_ubuntu-1804-kube-v1.16.3\u0026#39; # (required) The VM template to use for your management cluster. export VSPHERE_DISK_GIB=\u0026#39;50\u0026#39; # (optional) The VM Disk size in GB, defaults to 20 if not set export VSPHERE_NUM_CPUS=\u0026#39;2\u0026#39; # (optional) The # of CPUs for control plane nodes in your management cluster, defaults to 2 if not set export VSPHERE_MEM_MIB=\u0026#39;2048\u0026#39; # (optional) The memory (in MiB) for control plane nodes in your management cluster, defaults to 2048 if not set export SSH_AUTHORIZED_KEY=\u0026#39;ssh-rsa AAAAB...6Ix0= kuro@kuro-ubuntu01\u0026#39; # (optional) The public ssh authorized key on all machines in this cluster # Kubernetes configs export KUBERNETES_VERSION=\u0026#39;1.16.3\u0026#39; # (optional) The Kubernetes version to use, defaults to 1.16.2 export SERVICE_CIDR=\u0026#39;100.64.0.0/13\u0026#39; # (optional) The service CIDR of the management cluster, defaults to \u0026#34;100.64.0.0/13\u0026#34; export CLUSTER_CIDR=\u0026#39;100.96.0.0/11\u0026#39; # (optional) The cluster CIDR of the management cluster, defaults to \u0026#34;100.96.0.0/11\u0026#34; export SERVICE_DOMAIN=\u0026#39;cluster.local\u0026#39; # (optional) The k8s service domain of the management cluster, defaults to \u0026#34;cluster.local\u0026#34; 続けて、このファイルを使って、マネジメントクラスタの構成を定義したマニフェストファイル群を生成します。この作業のための専用のコンテナイメージが用意されているので、これに食べさせます。\n$ docker run --rm \\ -v \u0026#34;$(pwd):/out\u0026#34; \\ -v \u0026#34;$(pwd)/envvars.txt\u0026#34;:/envvars.txt:ro \\ gcr.io/cluster-api-provider-vsphere/release/manifests:latest \\ -c management-cluster Checking 192.168.0.201 for vSphere version Detected vSphere version 6.7.1 Generated ./out/management-cluster/addons.yaml Generated ./out/management-cluster/cluster.yaml Generated ./out/management-cluster/controlplane.yaml Generated ./out/management-cluster/machinedeployment.yaml Generated /build/examples/pre-67u3/provider-components/provider-components-cluster-api.yaml Generated /build/examples/pre-67u3/provider-components/provider-components-kubeadm.yaml Generated /build/examples/pre-67u3/provider-components/provider-components-vsphere.yaml Generated ./out/management-cluster/provider-components.yaml WARNING: ./out/management-cluster/provider-components.yaml includes vSphere credentials これで、./out/management-cluster 配下にマニフェストファイル群が出力されました。\n$ ls -l ./out/management-cluster/ total 268 -rw-r--r-- 1 kuro kuro 19656 Feb 11 08:54 addons.yaml -rw-r--r-- 1 kuro kuro 933 Feb 11 08:54 cluster.yaml -rw-r--r-- 1 kuro kuro 3649 Feb 11 08:54 controlplane.yaml -rw-r--r-- 1 kuro kuro 2576 Feb 11 08:54 machinedeployment.yaml -rw-r--r-- 1 kuro kuro 240747 Feb 11 08:54 provider-components.yaml で、あとはこれらを clusterctl に食べさせるだけです。\n$ clusterctl create cluster \\ --bootstrap-type kind \\ --bootstrap-flags name=management-cluster \\ --cluster ./out/management-cluster/cluster.yaml \\ --machines ./out/management-cluster/controlplane.yaml \\ --provider-components ./out/management-cluster/provider-components.yaml \\ --addon-components ./out/management-cluster/addons.yaml \\ --kubeconfig-out ./out/management-cluster/kubeconfig NOTICE: clusterctl has been deprecated in v1alpha2 and will be removed in a future version. I0211 08:55:09.414116 2332 createbootstrapcluster.go:27] Preparing bootstrap cluster I0211 08:55:59.745617 2332 clusterdeployer.go:82] Applying Cluster API stack to bootstrap cluster ... I0211 08:56:02.302440 2332 clusterdeployer.go:87] Provisioning target cluster via bootstrap cluster ... I0211 08:56:02.454960 2332 applymachines.go:46] Creating machines in namespace \u0026#34;default\u0026#34; I0211 08:59:12.512879 2332 clusterdeployer.go:105] Creating target cluster ... I0211 08:59:13.394821 2332 clusterdeployer.go:123] Pivoting Cluster API stack to target cluster ... I0211 08:59:56.665878 2332 clusterdeployer.go:164] Done provisioning cluster. You can now access your cluster with kubectl --kubeconfig ./out/management-cluster/kubeconfig I0211 08:59:56.666586 2332 createbootstrapcluster.go:36] Cleaning up bootstrap cluster. 主要なログだけ抜粋していますが、これだけで、\nKind を使って、作業端末の Docker 上にブートストラップクラスタを構築する ブートストラップクラスタに Cluster API を導入する その Cluster API を使って、マニフェスト通りに vSphere 環境上にマネジメントクラスタをデプロイする 仮想マシンをテンプレートからデプロイする 仮想マシンをクラスタのコントロールプレーンとして構成する ブートストラップクラスタ上に構成していた Cluster API の環境をマネジメントクラスタに移行する（Pivoting） が実行され、最終的な形でマネジメントクラスタができあがります。\n完成したマネジメントクラスタへの接続に必要な情報は、./out/management-cluster/kubeconfig に保存されています。kubectl の設定ファイルをこれに切り替えてコマンドを実行すると、マネジメントクラスタ自体の情報や、machine リソースとしての自分自身の存在が確認できます。\n$ export KUBECONFIG=\u0026#34;$(pwd)/out/management-cluster/kubeconfig\u0026#34; $ kubectl cluster-info Kubernetes master is running at https://192.168.0.27:6443 KubeDNS is running at https://192.168.0.27:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use \u0026#39;kubectl cluster-info dump\u0026#39;. $ kubectl get machines NAME PROVIDERID PHASE management-cluster-controlplane-0 vsphere://42069f1f-21ac-45be-69b9-e94702d3062b running vSphere 環境では、仮想マシン management-cluster-controlplane-0 の存在が確認できるはずです。\nここまででマネジメントクラスタができたので、Cluster API 環境は完成です。あとは好きなように Cluster API を使ってゲストクラスタを作ったり消したり拡張したり縮小したりできます。\nゲストクラスタの構成 実際に、Cluster API を使って、新しくゲストクラスタを構成します。\nCluster API は、Kubernetes のお作法で Kubernetes クラスタ自体が管理できるので、つまり、クラスタの構成情報も、Pod や Deployment などほかの Kubernetes リソースと同じように、マニフェストファイルで定義されます。よって、ゲストクラスタを作るには、その構成を定義したマニフェストファイルが必要です。\n本来はきちんと中身を書くべきっぽいですが、マネジメントクラスタ用のマニフェストファイルを作ったのと同じ方法でゲストクラスタ用のマニフェストファイル群も作れるので、ここではそれを利用します。\n$ docker run --rm \\ -v \u0026#34;$(pwd):/out\u0026#34; \\ -v \u0026#34;$(pwd)/envvars.txt\u0026#34;:/envvars.txt:ro \\ gcr.io/cluster-api-provider-vsphere/release/manifests:latest \\ -c workload-cluster-1 Checking 192.168.0.201 for vSphere version Detected vSphere version 6.7.1 Generated ./out/workload-cluster-1/addons.yaml Generated ./out/workload-cluster-1/cluster.yaml Generated ./out/workload-cluster-1/controlplane.yaml Generated ./out/workload-cluster-1/machinedeployment.yaml Generated /build/examples/pre-67u3/provider-components/provider-components-cluster-api.yaml Generated /build/examples/pre-67u3/provider-components/provider-components-kubeadm.yaml Generated /build/examples/pre-67u3/provider-components/provider-components-vsphere.yaml Generated ./out/workload-cluster-1/provider-components.yaml WARNING: ./out/workload-cluster-1/provider-components.yaml includes vSphere credentials ゲストクラスタの定義と、コントロールプレーンの定義が、\n./out/workload-cluster-1/cluster.yaml ./out/workload-cluster-1/controlplane.yaml に含まれます。また、ワーカノードの定義は、\n./out/workload-cluster-1/machinedeployment.yaml です。自分でマニフェストをいじる場合は、この辺をどうにかする必要があるということですね。\n実際にデプロイするには、kubectl の接続先をマネジメントクラスタに切り替えてから、先の 3 つのファイルをマネジメントクラスタに突っ込みます。\n$ export KUBECONFIG=\u0026#34;$(pwd)/out/management-cluster/kubeconfig\u0026#34; $ kubectl apply -f ./out/workload-cluster-1/cluster.yaml cluster.cluster.x-k8s.io/workload-cluster-1 created vspherecluster.infrastructure.cluster.x-k8s.io/workload-cluster-1 created $ kubectl apply -f ./out/workload-cluster-1/controlplane.yaml kubeadmconfig.bootstrap.cluster.x-k8s.io/workload-cluster-1-controlplane-0 created machine.cluster.x-k8s.io/workload-cluster-1-controlplane-0 created vspheremachine.infrastructure.cluster.x-k8s.io/workload-cluster-1-controlplane-0 created $ kubectl apply -f ./out/workload-cluster-1/machinedeployment.yaml kubeadmconfigtemplate.bootstrap.cluster.x-k8s.io/workload-cluster-1-md-0 created machinedeployment.cluster.x-k8s.io/workload-cluster-1-md-0 created vspheremachinetemplate.infrastructure.cluster.x-k8s.io/workload-cluster-1-md-0 created この作業によって、まずコントロールプレーンがデプロイされ、続けてワーカノードがデプロイされます。vSphere 環境でも順次仮想マシンがデプロイされパワーオンされていく様子が観察できるでしょう。\nデプロイが完了すると、以下のように、マネジメントクラスタが Kubernetes クラスタ自体を cluster リソースや machine リソースとして管理できている状態になります。\n$ kubectl get clusters NAME PHASE management-cluster provisioned workload-cluster-1 provisioned $ kubectl get machines NAME PROVIDERID PHASE management-cluster-controlplane-0 vsphere://42069f1f-21ac-45be-69b9-e94702d3062b running workload-cluster-1-controlplane-0 vsphere://4206f835-1e8d-3473-943f-0e2cc4b04319 running workload-cluster-1-md-0-78469c8cf9-fr22j vsphere://4206661b-9be8-cede-eeee-68ddbbdeb872 running workload-cluster-1-md-0-78469c8cf9-jnqnw vsphere://4206861f-e133-4a53-2008-3346c81ed8e3 running ゲストクラスタへの接続情報は、secret リソースとして保持されています。これを kubeconfig として保存することで、kubectl でゲストクラスタに接続できるようになります。\n$ kubectl get secrets NAME TYPE DATA AGE ... workload-cluster-1-kubeconfig Opaque 1 ... $ kubectl get secret workload-cluster-1-kubeconfig -o=jsonpath=\u0026#39;{.data.value}\u0026#39; | \\ { base64 -d 2\u0026gt;/dev/null || base64 -D; } \u0026gt;./out/workload-cluster-1/kubeconfig 実際に接続すれば、当たり前ですがゲストクラスタやノードの情報が確認できます。\n$ export KUBECONFIG=\u0026#34;$(pwd)/out/workload-cluster-1/kubeconfig\u0026#34; $ kubectl cluster-info Kubernetes master is running at https://192.168.0.28:6443 KubeDNS is running at https://192.168.0.28:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy To further debug and diagnose cluster problems, use \u0026#39;kubectl cluster-info dump\u0026#39;. $ kubectl get nodes NAME STATUS ROLES AGE VERSION workload-cluster-1-controlplane-0 NotReady master 3m14s v1.16.3 workload-cluster-1-md-0-78469c8cf9-fr22j NotReady \u0026lt;none\u0026gt; 90s v1.16.3 表示されている通り、この段階ではノードは NotReady です。これは CNI プラグインが構成されていないからで、雑にいえば、この Kubernetes クラスタ内のコンテナネットワークに使う実装を明示する必要があるということです。\nコンテナネットワークの実装には Flannel とか Calico とかいろいろありますが、ゲストクラスタ用マニフェスト群の中にある ./out/workload-cluster-1/addons.yaml で Calico を構成できるので、今回はこれを使います。\n適用してしばらく待つと、ノードが Ready になり、ゲストクラスタの完成です。\n$ kubectl apply -f ./out/workload-cluster-1/addons.yaml configmap/calico-config created ... serviceaccount/calico-kube-controllers created $ kubectl get nodes NAME STATUS ROLES AGE VERSION workload-cluster-1-controlplane-0 Ready master 4m22s v1.16.3 workload-cluster-1-md-0-78469c8cf9-fr22j Ready \u0026lt;none\u0026gt; 2m38s v1.16.3 ゲストクラスタの拡張 Kubernetes って、アプリケーションのスケールアウトが kubectl scale だけでできてめっちゃラクですよね。\nというのと同じノリで、ゲストクラスタもめっちゃラクにスケールアウトできるので、やってみましょう。\nノードの管理はマネジメントクラスタから行うので、接続先を切り替えて、machinedeployment をスケールさせます。\n$ export KUBECONFIG=\u0026#34;$(pwd)/out/management-cluster/kubeconfig\u0026#34; $ kubectl scale md workload-cluster-1-md-0 --replicas=3 machinedeployment.cluster.x-k8s.io/workload-cluster-1-md-0 scaled $ kubectl get machines NAME PROVIDERID PHASE management-cluster-controlplane-0 vsphere://42069f1f-21ac-45be-69b9-e94702d3062b running workload-cluster-1-controlplane-0 vsphere://4206f835-1e8d-3473-943f-0e2cc4b04319 running workload-cluster-1-md-0-78469c8cf9-fr22j vsphere://4206661b-9be8-cede-eeee-68ddbbdeb872 running workload-cluster-1-md-0-78469c8cf9-jnqnw vsphere://4206861f-e133-4a53-2008-3346c81ed8e3 running workload-cluster-1-md-0-78469c8cf9-nxvht vsphere://4206ec88-6607-1699-8051-e1447a448983 running これだけでノードが 3 台になりました。仮想マシンも増えています。\n簡単ですね。\nゲストクラスタのロードバランサの構成 おまけです。\nvSphere が組み込みでロードバランサを持っていないから仕方ないですが、 現状、Cluster API でゲストクラスタを構成するときには、ロードバランサは構成できません。\nこのままだと、ゲストクラスタで kubectl expose で --type=LoadBalancer しても EXTERNAL-IP が永遠に pending のままで、外部に公開できません。\nGitHub でも issue があります し、めっちゃがんばると多分オンプレミス環境でも NSX-T とかでどうにかできるとは思いますが、その域に達していないので、とりあえず MetalLB を突っ込んで解決します。\nMetalLB とは ロードバランサが使えずにサービスを外部に公開できない、というのは、ベアメタル環境で Kubernetes を使うときによく遭遇する話題のようで、そういうヒト向けの仮想ロードバランサの実装です。\n実際には正しい負荷分散にはならないみたいですが、ものすごく気軽に使えますし、アクセス経路の提供という意味では充分機能するので便利です。\nMetalLB の構成と初期設定 公式のドキュメント に従います。インストール用のマニフェストファイルを突っ込んだあと、\n$ kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.8.3/manifests/metallb.yaml namespace/metallb-system created ... deployment.apps/controller created ドキュメントの Layer 2 Configuration の通りに設定用マニフェストファイルを作って突っ込みます。IP アドレスのレンジは任意で修正します。\n$ cat metallb.yaml apiVersion: v1 kind: ConfigMap metadata: namespace: metallb-system name: config data: config: | address-pools: - name: default protocol: layer2 addresses: - 192.168.0.50-192.168.0.99 $ kubectl apply -f metallb.yaml configmap/config created これだけです。\n動作確認 Kubernetes のチュートリアルのヤツ を作って、\n$ kubectl apply -f https://k8s.io/examples/service/load-balancer-example.yaml deployment.apps/hello-world created $ kubectl get pods NAME READY STATUS RESTARTS AGE hello-world-f9b447754-6rrt9 1/1 Running 0 68s hello-world-f9b447754-b6psq 1/1 Running 0 68s hello-world-f9b447754-ml5w8 1/1 Running 0 68s hello-world-f9b447754-nprbn 1/1 Running 0 68s hello-world-f9b447754-wpgv5 1/1 Running 0 68s expose します。\n$ kubectl expose deployment hello-world --type=LoadBalancer service/hello-world exposed $ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE hello-world LoadBalancer 100.69.189.221 192.168.0.50 8080:32015/TCP 6s kubernetes ClusterIP 100.64.0.1 \u0026lt;none\u0026gt; 443/TCP 4h55m $ kubectl describe services hello-world ... Endpoints: 100.113.190.67:8080,100.113.190.68:8080,100.97.109.3:8080 + 2 more... ... EXTERNAL-IP に MetalLB で設定したレンジの IP アドレスが振られていますし、エンドポイントも 5 つです。\n実際にこの IP アドレスにアクセスすると、正しく表示が返ってきます。無事に動いているようです。\n$ curl http://192.168.0.50:8080/ Hello Kubernetes! まとめ vSphere 環境で Cluster API を使って Kubernetes クラスタを管理できる状態を整えました。\n最初の作業はちょっとだけ手間ですが、いちどできてしまうとスケールも相当楽なので使いやすそうです。ロードバランサ周りが NSX-T などふくめいい感じにできるようになってくると、活用の幅も広がりそうですね。\nVMware Tanzu や Project Pacific でも Kubernetes クラスタのライフサイクル管理は謳われているので、この手の作業が導入作業も含めて GUI や API 経由でポチポチ簡単にできるようになるのだろうと勝手に思っています。GA になったら触ってみます。\n","date":"2020-02-11T16:59:30Z","image":"/archives/2850/img/25.png","permalink":"/archives/2850/","title":"Cluster API で vSphere 上の Kubernetes クラスタを管理する"},{"content":"これまでの LINE ボットの課題 2018 年に、家のエアコンを操作してくれる LINE ボットを作って以来、すでに一年半くらい運用しています。概要は 当時のエントリでも紹介しています が、LINE での会話を元に自宅のエアコンを操作してくれるものです。\nこれ、作った当初に想定していた以上にたいへん便利で、外出先で家族と『そろそろロボくんにお願いしておこう』などの会話が発生する程度には実際に活用されていました。この手の『作ってみた』系は、長期的な運用が定着する前に使わなくなることが多い印象もあり、これは小規模ではあるもののうまくいった例と言えるかと思います。\n一方で、作りが甘い部分もあって、\nLambda の Node.js のランタイムで EoL が迫っていた そもそも Node.js のランタイム側の更新に追従していくことに将来的にけっこう体力を使いそうな印象がある 当時 Node.js を選んだ理由は『とりあえずいちど触ってみたかった』からというだけで、すでにその目的は達成できた ひとつの Lambda に全機能を詰め込んでいて、どう考えてもイケてないアーキテクチャだ 機能が足りない エアコンのオンオフと温度の変更はできたが、冷房と暖房の切り替え機能を実装していない など、長期的な運用に耐えられるよう全体を作り直したいモチベーションも強くなってきていました。\nそんな中、あるイベントに参加して、『Raspberry Pi とセンサとクラウドサービスを使って何でもいいから個人で何かを作る』という活動をすることになり、タイミングもよかったので、この LINE ボットの作り直しを進めることにしました。\nできたもの で、こういう風に進化して、今も元気に動いています。\n以下のような機能が付いています。\nエアコンの操作 オンオフ、冷暖房の切り替え、温度の変更、現在の設定の確認をしてくれます 加湿器の操作 オンオフをしてくれます 空調センサ 室内に設置したセンサを使って、気温、湿度、気圧、二酸化炭素濃度の現在の値を教えてくれます 任意の時間分の履歴をグラフ化して送ってくれます 家計簿の記録 支払者、使途、金額を Google Sheets に追記してくれます 毎朝の自動制御 毎朝、部屋が寒い（暑い）場合は、暖房（冷房）を付けてくれます 毎朝、部屋が乾燥している場合は、加湿器を付けてくれます 実装 実装を簡単に紹介します。末尾には GitHub へのリンクも載せています。\n全体像 全体はこのような構成です。\n参加したイベントの性質上、なるべくたくさんの要素技術を取り込んだほうが評価が上がる仕組みだったため、あえてまわりくどく冗長な構成になっている部分もあります。\nセンシングと蓄積 センシングと蓄積は、図の以下の部分です。これまで複数回にわたって紹介してきた EdgeX Foundry を中心に構成しています。\nEdgeX Foundry 自体は、家庭内の IoT ゲートウェイサーバとして構築した Ubuntu 仮想マシン（ESXi 上で動作）の中で動いています。このゲートウェイサーバ上では MQTT ブローカも動かしています。\nRaspberry Pi は、センサ値を読んで MQTT トピックにひたすら放り込み続けるだけの係です。EdgeX Foundry は、この Raspberry Pi を MQTT デバイスとして制御するよう構成されており、所定の MQTT トピックに投げ込まれたデータを取り込んで、アプリケーションサービスの機能を使ってクラウド上の所定の宛先にデータをエクスポートしています。\nエクスポート先は、Mosquitto のパブリック MQTT トピックと、Pivotal Web Service（PWS）上のワーカアプリケーションで、なんやかんやされて最終的にデータは InfluxDB Cloud 2.0、Redis Cloud、MongoDB mLab に蓄積されます。\n十数年前に PIC や Arduino でセンサやアクチュエータを操作していた頃は、データシートとにらめっこをしながら必要な抵抗を計算したり回路を考えたりしていましたが、最近のヤツは単にデータを読み取るだけなら恐ろしいくらい簡単ですね…… けっこう衝撃でした。\n今回は BME280 と MH-Z19 を使っています。安いですし精度は悪いのでしょうが、厳密な値は必要としていないのでよしとしています。最初は DS18B20 も使っていましたが、BME280 を組み込んでからは使っていません。\nアクチュエーション 最終的にはエアコンと加湿器が操作されるわけですが、この部分の実装は、前者は Nature Remo の API、後者は非公式の野良 API で制御しています。\nNature Remo の API を叩く役は、AWS の所定の Lambda に一任しています。加湿器も同様ですが、こちらは非公式 API の都合上、ローカルネットワーク内からしか制御できなかったので、Lambda からいちど IoT Core の MQTT トピックに命令をなげ、それをローカルネットワーク側から購読して後続の処理をトリガさせています。ここだけは Node.js です。\n加湿器は、モデルの選定がなかなかたいへんでした。加湿器に限らず何らかの家電を外部から自動制御する場合、\nスマートコンセントを利用する コンセントが通電したら目的の動作が開始される仕様である必要がある スマートスイッチ（SwitchBot など）を利用する 目的のボタンが、物理的にスマートスイッチで押下できる形状であり、かつスマートスイッチの力で押せるだけの硬さである必要がある スマートリモコンを利用する RF ではなく赤外線を利用したリモコンで制御できる機器である必要がある 専用の API を利用する API が公開されている必要がある のいずれかの条件を満たす必要がありますが、加湿器でこれに合致するモデルが全然見つけられませんでした。大体の加湿器は、コンセントの通電後にさらにボタンを押さないと動作しませんし、ボタンの周辺の形状や寸法やボタンの硬さは公開されていませんし、リモコン付きのモデルでも赤外線でなく RF ですし、公式の API はなさそうですし。\n結局、公開されていない API を無理やり叩ける非公式のライブラリが存在していてハックの余地がありそう、ということがわかった Oittm のアロマディフューザー を採用しています。ただしこれも、\n非公式のライブラリ で制御できるようにするには、公式アプリケーション Tuya Smart の旧バージョン（3.12.6 以前）を使って、MITM 攻撃的なヤツでアプリケーションの通信を自分で盗聴してキーを入手しないといけない ライブラリが Node.js 向けしかない そもそもの加湿器自体が小型でとても非力で、リビングなど広い空間の加湿には向かない など、ベストとは言い難い状態です。改善の余地ありです。\nインタフェイスとインタラクション インタフェイス役の LINE ボットに話しかけると、LINE の Messaging API 経由で AWS の Lambda に届いて、メッセージ本文がパタンマッチされてその内容に応じて後続の処理がトリガされます。\nエアコンの操作であれば Nature Remo を制御する Lambda を呼びますし、加湿器の操作であればそれ用の別の Lambda を呼びます。センサのデータが必要な処理であれば、外部のデータベースに必要なデータを取りに行く Lambda を呼びます。\nグラフを要求された場合は、InfluxDB からデータを取ってきて matplotlib で描画したあとに S3 に保存し、それをプッシュでユーザに送ります。描画する対象（温度、湿度、気圧……）と長さ（N 分、M 時間）はメッセージ本文から都度判断します。\nこの辺りの処理では、\nグラフ要求のメッセージが来たら、受理した旨の返事を後続処理に先行して即座にしてしまい、画像の生成と送信はあとから非同期で行う グラフの生成に時間がかかるため、 同期的に処理させると（Lambda ではなく）API Gateway がタイムアウトする S3 のバケット名や画像ファイル名をなるべく短くする LINE で画像を送るときは、画像そのものではなく画像の URL を送る必要がある Lambda で発行する S3 の署名付き URL は 1,000 文字前後とめちゃくちゃ長い（x-amz-security-token が含まれるため） LINE 側の制約で、URL は 1,000 文字以内でないとエラーで送れない 署名付き URL ではなくパブリックな URL にしてしまうのは気が引ける バケット名やファイル名は URL に含まれるため、短くすることでギリギリ 1,000 文字以内になる などの工夫が必要でした。特に二点目はひどい回避策ですが、あまり権限をがばがばにはしたくなかったので仕方なく……。\nまた、LINE とはまったく関係ないところで、Grafana を PWS で動かしており、そこでもグラフを見られるようにしています。\n感触とロードマップ センサの値がグラフで見られるのが、実際に使ってみると思っていた以上におもしろかったです。\n例えばエアコンをつけてから温度が上がっていく様子や、朝起きて部屋で人間が活動しだしてから二酸化炭素濃度が一気に上がる様子、逆に家が無人になってから下がる様子、キッチンで火を使う調理を始めたタイミングなど、思っていた以上に生活パタンが可視化されることがわかりました。\nまた、二酸化炭素濃度や気圧の変化では眠気や頭痛の誘発なども懸念されるので、体調に違和感を覚えたときに客観的な変化をすぐ確認できるのはうれしく、体調管理面でも地味に役立っています。\n運用面では、Lambda まわりをすべて CloudFormation で定義したことで、コードの管理とデプロイがだいぶに楽になりました。クラウド側がマネージドサービスとサーバレスアーキテクチャだけで組めているのも安心感があります。\nただ、本エントリの途中で、\n参加したイベントの性質上、なるべくたくさんの要素技術を取り込んだほうが評価が上がる仕組みだったため、あえてまわりくどく冗長な構成になっている部分もあります\nと書いた通り、正直なところ機能に比較して実装が大げさすぎるので、この辺はスリムにしたいと思っています。\nイベントはもう終わったので、現状のゴテゴテ感を保つ意味はあまりなく、例えば、\nこの規模だと EdgeX Foundry の恩恵は受けにくいので、まるっと削って Raspberry Pi から直接クラウドに投げてもよいだろう データの蓄積場所も AWS の DynamoDB などにしてしまえば、PWS も InfluxDB も Redis も MongoDB もいらなくなり、AWS に全部寄せられるだろう いっそ Raspberry Pi も AWS IoT Greengrass で AWS から管理させるとよいのでは などのダイエットや試行錯誤を検討中です。とくに Greengrass は完全に未修分野なので純粋に興味もあり触ってみたいですね。\n物理的には、加湿器をもう少し高機能（大容量）かつ自動制御しやすいものに替えたい気持ちがあります。オンオフは自動で制御できても、給水が自動化しづらいので、結局は人間の介入が必要な状態になってしまっており、人間が手を抜くにはタンクの容量がキモになりそうだと考えています。\n関連リポジトリ 一覧します。\nraspi-airmeasurment Raspberry Pi 上で動作させる、各種センサ値を読み取って MQTT トピックに送る Python プログラム edgex-lab-raspi Raspberry Pi から MQTT トピックに送られた値の取り込みや、クラウドへのエクスポートを行えるように構成した EdgeX Foundry の Docker Compose ファイル edgex-lab-export2db PWS 上で動作させる、EdgeX Foundry からエクスポートされたデータを各種データベースに保存するためのプログラム群 Telegraf 用のイメージは Docker Hub の kurokobo/edgex-lab-telegraf に配置済み grafana-with-flux PWS 上で動作させる Grafana sam-smarthome-api AWS 上で動作させる一連の Lambda 群 tuya-mqtt MQTT の配信を受けて加湿器をコントロールする Node.js プログラム ","date":"2020-02-09T15:19:59Z","image":"/archives/2805/img/21.png","permalink":"/archives/2805/","title":"おうち IoT 用の LINE ボットをもう少し賢くする"},{"content":"EdgeX Foundry 関連エントリ 冬休みの自由研究： EdgeX Foundry (1) 概要 冬休みの自由研究： EdgeX Foundry (2) 導入とデータの確認 冬休みの自由研究： EdgeX Foundry (3) エクスポートサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (4) MQTT デバイスサービスの追加 冬休みの自由研究： EdgeX Foundry (5) ルールエンジンによる自動制御 冬休みの自由研究： EdgeX Foundry (6) アプリケーションサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (7) Kubernetes 上で Fuji リリースを動かす EdgeX Foundry ハンズオンラボガイド公開 EdgeX Foundry： Geneva から Hanoi へ エクスポート方法の新旧 エクスポートサービスを取り扱ったエントリの最後で、以下のように書きました。\n……と、意気揚々と書いてきたものの、今回利用したエクスポートサービスは最近のリリースではすでに廃止されており、エクスポートの機能は アプリケーションサービス に統合されているようです。 冬休みの自由研究： EdgeX Foundry (3) エクスポートサービスによるエクスポート\rアプリケーションサービスに関する公式ドキュメント では、エクスポートサービスの利用はまだサポートされるものの、今後はアプリケーションサービスの利用が推奨されています。\nエクスポートサービスを利用したエクスポートの欠点として、\nエクスポートサービスは、登録されているすべてのクライアントにデータを順次配信するが、これがボトルネックになり、エンドポイント側の受信が遅延する 登録されているクライアントを管理することそれ自体のオーバヘッドと複雑さが EdgeX Foundry にとって負担である 特定のクラウドへのエクスポートに限定して作りこむのではなく、SDK を提供してクラウドプロバイダにとらわれない状態にすべきだ などが挙げられています。ここでいう SDK とそれを使った実装がアプリケーションサービスなわけですが、アプリケーションサービスでは、\nコアサービス（データを ZeroMQ で配る Core Data）に直接つなげられることで、パフォーマンスの問題を排除する 開発者には、データが利用可能になったら直ちにそれに対して何らかの処理を行える手段が提供される 登録作業が不要である などの点でよい感じになっています。\nいまいち正しくない気もしますが、ざっくりまとめると、下図の上が下になったので、\nエクスポートサービス（のディストリビューションサービス）がボトルネックにならなくなったし、値を好きに処理できる場所もできたよ！ ということかと思います。\n今回試すこと そういうわけで、エクスポートサービスのなくなった世界でも生きていけるよう、エクスポートサービスを扱ったエントリと同様に、MQTT トピックと REST エンドポイントへ、アプリケーションサービスを使ってエクスポートできる状態を作ります。\n今回も GitHub にもろもろ置いてあります ので、こちらをクローンして使います。\ngit clone https://github.com/kurokobo/edgex-lab-handson.git cd lab05 アプリケーションサービス 上図のアプリケーションサービスの箱の中には、ファンクション（Fn で表記）を並べました。アプリケーションサービスでは、このように任意のファンクション群をパイプライン化してデータフローを定義できます。\n例えば簡単な例では、標準で用意されているファンクションを並べるだけでも、\nデバイス名でフィルタするファンクション リソース名でフィルタするファンクション JSON 形式に変換するファンクション MQTT でエクスポートするファンクション などのパイプラインが構成できます。現時点で利用できるファンクションは、SDK の README.md で確認できます。\n構成の考え方 標準で実装されているアプリケーションサービスとして、app-service-configurable がありますが、これは、ひとつのパイプラインをひとつのインスタンスで処理する前提で作られているようです。また、パイプラインそのものは、インスタンスごとの設定ファイル configuration.toml で定義します。\nつまり、コンテナ環境の例でいえば、冒頭の絵のように、\n最終的に MQTT トピックにエクスポートする app-service-configurable コンテナ 最終的に REST エンドポイントにエクスポートする app-service-configurable コンテナ など、app-service-configurable が、目的に応じて複数起動している状態を作るということです。\nなお、コアサービスのホスト名やポート番号、MQTT ではブローカのホスト名など環境に依存する情報は、コンテナの場合は環境変数で Docker Compose ファイルから渡せるようになっています。エクスポートサービスでは API を叩いてクライアントとして登録する作業が必要でしたが、すべての構成をあらかじめファイルとして定義しておけるということですね。\n設定ファイルの確認 コンテナの場合、デフォルトでコンテナ内の /res に、目的別に事前構成された configuration.toml がディレクトリを分けて配置されています。そして、起動時に渡す環境変数で、どのディレクトリの中身を利用するか指定します。\n例えば、MQTT トピックへのエクスポートで利用する configuration.toml は以下です。\n$ cat mqtt-export/configuration.toml ... [Writable.Pipeline] ExecutionOrder = \u0026#34;TransformToJSON, MQTTSend, MarkAsPushed\u0026#34; [Writable.Pipeline.Functions.TransformToJSON] [Writable.Pipeline.Functions.MarkAsPushed] [Writable.Pipeline.Functions.FilterByDeviceName] [Writable.Pipeline.Functions.FilterByDeviceName.Parameters] DeviceNames = \u0026#34;\u0026#34; [Writable.Pipeline.Functions.FilterByValueDescriptor] [Writable.Pipeline.Functions.FilterByValueDescriptor.Parameters] ValueDescriptors = \u0026#34;\u0026#34; [Writable.Pipeline.Functions.MQTTSend] [Writable.Pipeline.Functions.MQTTSend.Parameters] qos=\u0026#34;0\u0026#34; key=\u0026#34;\u0026#34; autoreconnect=\u0026#34;false\u0026#34; retain=\u0026#34;false\u0026#34; cert=\u0026#34;\u0026#34; persistOnError = \u0026#34;false\u0026#34; [Writable.Pipeline.Functions.MQTTSend.Addressable] Address= \u0026#34;localhost\u0026#34; Port= 1883 Protocol= \u0026#34;tcp\u0026#34; Publisher= \u0026#34;AppServiceConfigurable-mqtt-export\u0026#34; User= \u0026#34;\u0026#34; Password= \u0026#34;\u0026#34; Topic= \u0026#34;edgex-events\u0026#34; ... ExecutionOrder に含まれる関数がパイプラインとして順次実行されていきます。この例だと、JSON に変換してから MQTT トピックに送信しています。\n利用する関数は下で列挙されていますが、ExecutionOrder に含まれないものは実際には利用されないようです。デバイス名でフィルタする関数（FilterByDeviceName）なども書かれていますが、使いたいなら自分で ExecutionOrder に書いてね、ということでしょう。\nまた、MQTT ブローカのホスト名に localhost が指定されていたり、トピック名が edgex-events になっていたりしますが、先述のとおり、この辺りは環境変数で書き換えますのでこのままで問題ありません。\n同様に、REST エンドポイントへのエクスポートで利用する設定ファイルは以下です。\n$ cat http-export/configuration.toml ... [Writable.StoreAndForward] Enabled = false RetryInterval = \u0026#34;5m\u0026#34; MaxRetryCount = 10 [Writable.Pipeline] UseTargetTypeOfByteArray = false ExecutionOrder = \u0026#34;FilterByDeviceName, TransformToJSON, HTTPPostJSON, MarkAsPushed\u0026#34; [Writable.Pipeline.Functions.TransformToJSON] [Writable.Pipeline.Functions.MarkAsPushed] [Writable.Pipeline.Functions.FilterByDeviceName] [Writable.Pipeline.Functions.FilterByDeviceName.Parameters] DeviceNames = \u0026#34;\u0026#34; [Writable.Pipeline.Functions.FilterByValueDescriptor] [Writable.Pipeline.Functions.FilterByValueDescriptor.Parameters] ValueDescriptors = \u0026#34;\u0026#34; [Writable.Pipeline.Functions.HTTPPostJSON] [Writable.Pipeline.Functions.HTTPPostJSON.Parameters] url = \u0026#34;http://\u0026#34; persistOnError = \u0026#34;false\u0026#34; ... 環境変数による柔軟なオーバライド 設定ファイルに含まれるパラメータは、実装から推測すると、おそらくすべて環境変数で書き換えが可能です。セクション名の区切り文字を _ に置換して、さらに _\u0026lt;パラメータ名\u0026gt; を付与して環境変数として与えれば、オーバライドされるようでした。\n例えば、ExecutionOrder 自体も、Writable_Pipeline_ExecutionOrder 環境変数として与えられます。例えば先述の mqtt-export の ExecutionOrder には FilterByDeviceName が含まれせんが、環境変数で以下を与えることで、このフィルタも有効化できます。\n環境変数 Writable_Pipeline_ExecutionOrder FilterByDeviceName, TransformToJSON, MQTTSend, MarkAsPushed 環境変数 Writable_Pipeline_Functions_FilterByDeviceName_Parameters_DeviceNames \u0026lt;デバイス名\u0026gt; 今回の構成の定義 では、今回用の構成を作ります。先の通り、環境依存の値は環境変数経由で与えればよいので、今回編集するのは、Docker Compose ファイルです。\nDocker Compose ファイルには、もともと、ルールエンジンにデータを送る用の app-service-rules が定義されています。\n$ cat docker-composer.yml ... app-service-rules: image: edgexfoundry/docker-app-service-configurable:1.0.0 ports: - \u0026#34;48096:48096\u0026#34; container_name: edgex-app-service-configurable-rules hostname: edgex-app-service-configurable-rules networks: edgex-network: aliases: - edgex-app-service-configurable-rules environment: \u0026lt;\u0026lt;: *common-variables edgex_service: http://edgex-app-service-configurable-rules:48096 edgex_profile: rules-engine Service_Host: edgex-app-service-configurable-rules MessageBus_SubscribeHost_Host: edgex-core-data depends_on: - consul - logging - data ... 今回は、これに加えて、MQTT トピックにエクスポートする app-service-mqtt-export と、REST エンドポイントにエクスポートする app-service-http-export を追加しています。\n$ cat docker-composer.yml ... app-service-mqtt-export: image: edgexfoundry/docker-app-service-configurable:1.0.0 ports: - \u0026#34;48097:48097\u0026#34; container_name: edgex-app-service-configurable-mqtt-export hostname: edgex-app-service-configurable-mqtt-export networks: edgex-network: aliases: - edgex-app-service-configurable-mqtt-export environment: \u0026lt;\u0026lt;: *common-variables edgex_service: http://edgex-app-service-configurable-mqtt-export:48097 edgex_profile: mqtt-export Service_Host: edgex-app-service-configurable-mqtt-export MessageBus_SubscribeHost_Host: edgex-core-data Writable_Pipeline_Functions_MQTTSend_Addressable_Address: 192.168.0.100 Writable_Pipeline_Functions_MQTTSend_Addressable_Port: 1883 Writable_Pipeline_Functions_MQTTSend_Addressable_Protocol: tcp # Writable_Pipeline_Functions_MQTTSend_Addressable_Publisher: # Writable_Pipeline_Functions_MQTTSend_Addressable_User: # Writable_Pipeline_Functions_MQTTSend_Addressable_Password: Writable_Pipeline_Functions_MQTTSend_Addressable_Topic: edgex-handson-topic # Writable_Pipeline_Functions_MQTTSend_Parameters_Qos: # Writable_Pipeline_Functions_MQTTSend_Parameters_Key: # Writable_Pipeline_Functions_MQTTSend_Parameters_Cert: # Writable_Pipeline_Functions_MQTTSend_Parameters_Autoreconnect: # Writable_Pipeline_Functions_MQTTSend_Parameters_Retain: # Writable_Pipeline_Functions_MQTTSend_Parameters_PersistOnError: volumes: - ./app-service-configurable:/res depends_on: - consul - logging - data app-service-http-export: image: edgexfoundry/docker-app-service-configurable:1.0.0 ports: - \u0026#34;48098:48098\u0026#34; container_name: edgex-app-service-configurable-http-export hostname: edgex-app-service-configurable-http-export networks: edgex-network: aliases: - edgex-app-service-configurable-http-export environment: \u0026lt;\u0026lt;: *common-variables edgex_service: http://edgex-app-service-configurable-http-export:48098 edgex_profile: http-export Service_Host: edgex-app-service-configurable-http-export MessageBus_SubscribeHost_Host: edgex-core-data Writable_Pipeline_Functions_HTTPPostJSON_Parameters_url: http://192.168.0.100:5000/api/v1/echo # Writable_Pipeline_Functions_HTTPPostJSON_Parameters_persistOnError: volumes: - ./app-service-configurable:/res depends_on: - consul - logging - data ... 使っているコンテナイメージは、いずれもまったく同じです。ボリュームも同じマウントのしかたをしていますが、使う設定ファイルを edgex_profile で制御しています。また、読ませる設定ファイルに応じて、必要な設定を環境変数で入れています。\nMQTT ブローカや REST エンドポイントのホスト名などは環境に合わせて適宜修正してください。ほかにも、configuration.toml で変更したい値があれば、環境変数として与えると変えられます。\nまた、今回はエクスポートサービスが起動しないようにコメントアウトしています。\n起動 下準備として、MQTT ブローカを起動して、購読を開始します。\ndocker run -d --rm --name broker -p 1883:1883 eclipse-mosquitto docker run --init --rm -it efrecon/mqtt-client sub -h 192.168.0.100 -t \u0026#34;#\u0026#34; -v 別のターミナルで REST エンドポイントも起動します。\n$ cd rest-endpoint $ python ./main.py * Serving Flask app \u0026#34;main\u0026#34; (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: on * Restarting with stat * Debugger is active! * Debugger PIN: 159-538-312 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit) この状態で、EdgeX Foundry を起動します。\ndocker-compose up -d 動作確認 必要な設定はすべて環境変数を使って与えていたので、何もしないでも MQTT トピックと REST エンドポイントに値が届き出します。\n$ docker run --init --rm -it efrecon/mqtt-client sub -h 192.168.0.100 -t \u0026#34;#\u0026#34; -v ... edgex-handson-topic {\u0026#34;id\u0026#34;:\u0026#34;a11011bb-30cb-44fa-885c-8929c64e35b1\u0026#34;,\u0026#34;device\u0026#34;:\u0026#34;Random-UnsignedInteger-Device\u0026#34;,\u0026#34;origin\u0026#34;:1580015685340757500,\u0026#34;readings\u0026#34;:[{\u0026#34;id\u0026#34;:\u0026#34;26f50397-1e8b-4ca0-8db2-eeeee958a643\u0026#34;,\u0026#34;origin\u0026#34;:1580015685326443600,\u0026#34;device\u0026#34;:\u0026#34;Random-UnsignedInteger-Device\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;Uint8\u0026#34;,\u0026#34;value\u0026#34;:\u0026#34;90\u0026#34;}]} edgex-handson-topic {\u0026#34;id\u0026#34;:\u0026#34;526631fd-a0f7-437d-98a9-4f6eb8a06775\u0026#34;,\u0026#34;device\u0026#34;:\u0026#34;Random-UnsignedInteger-Device\u0026#34;,\u0026#34;origin\u0026#34;:1580015685369824800,\u0026#34;readings\u0026#34;:[{\u0026#34;id\u0026#34;:\u0026#34;91ef8066-5c31-4377-84e6-a6d517457f62\u0026#34;,\u0026#34;origin\u0026#34;:1580015685356418200,\u0026#34;device\u0026#34;:\u0026#34;Random-UnsignedInteger-Device\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;Uint16\u0026#34;,\u0026#34;value\u0026#34;:\u0026#34;27009\u0026#34;}]} ... $ python ./main.py ... -- 2020-01-26 14:15:10.318596 {\u0026#34;id\u0026#34;:\u0026#34;be20b21b-f0a4-4550-95bb-a78b5c597f56\u0026#34;,\u0026#34;device\u0026#34;:\u0026#34;Random-Integer-Device\u0026#34;,\u0026#34;origin\u0026#34;:1580015710310854200,\u0026#34;readings\u0026#34;:[{\u0026#34;id\u0026#34;:\u0026#34;d62fdacc-bfa4-4d05-a537-25c03e151f78\u0026#34;,\u0026#34;origin\u0026#34;:1580015710295318700,\u0026#34;device\u0026#34;:\u0026#34;Random-Integer-Device\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;Int8\u0026#34;,\u0026#34;value\u0026#34;:\u0026#34;3\u0026#34;}]} 192.168.0.100 - - [26/Jan/2020 14:15:10] \u0026#34;POST /api/v1/echo HTTP/1.1\u0026#34; 200 - -- 2020-01-26 14:15:10.375595 {\u0026#34;id\u0026#34;:\u0026#34;c0275732-2e3d-4945-9435-d6e77e0ccb74\u0026#34;,\u0026#34;device\u0026#34;:\u0026#34;Random-Integer-Device\u0026#34;,\u0026#34;origin\u0026#34;:1580015710364584600,\u0026#34;readings\u0026#34;:[{\u0026#34;id\u0026#34;:\u0026#34;067d71a3-7c1f-4fea-968d-ac5db0410e67\u0026#34;,\u0026#34;origin\u0026#34;:1580015710350134100,\u0026#34;device\u0026#34;:\u0026#34;Random-Integer-Device\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;Int16\u0026#34;,\u0026#34;value\u0026#34;:\u0026#34;-23040\u0026#34;}]} 192.168.0.100 - - [26/Jan/2020 14:15:10] \u0026#34;POST /api/v1/echo HTTP/1.1\u0026#34; 200 - ... 簡単ですね。\nこれらの値は、冒頭で紹介した通り、それぞれのアプリケーションサービスが直接コアサービス層から値を受け取って処理したうえでエクスポートしています。データパスが分散して並列化するので、エクスポートサービスを使っていた頃に存在していたボトルネックが回避されます。\nまとめ エクスポートサービスを利用しないエクスポート手段として、アプリケーションサービスを構成して試しました。\n使ってみると、確かにこのアーキテクチャのほうが素直で圧倒的に使い勝手が良いです。家の環境もこっちに置き換えました。\n現段階ではバンドルされている関数は多くないですが、今後この辺りも充実してくることが期待できそうです。\nEdgeX Foundry 関連エントリ 冬休みの自由研究： EdgeX Foundry (1) 概要 冬休みの自由研究： EdgeX Foundry (2) 導入とデータの確認 冬休みの自由研究： EdgeX Foundry (3) エクスポートサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (4) MQTT デバイスサービスの追加 冬休みの自由研究： EdgeX Foundry (5) ルールエンジンによる自動制御 冬休みの自由研究： EdgeX Foundry (6) アプリケーションサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (7) Kubernetes 上で Fuji リリースを動かす EdgeX Foundry ハンズオンラボガイド公開 EdgeX Foundry： Geneva から Hanoi へ ","date":"2020-01-26T05:34:23Z","image":"/archives/2787/img/DSC06315.jpg","permalink":"/archives/2787/","title":"冬休みの自由研究： EdgeX Foundry (6) アプリケーションサービスによるエクスポート"},{"content":"EdgeX Foundry 関連エントリ 冬休みの自由研究： EdgeX Foundry (1) 概要 冬休みの自由研究： EdgeX Foundry (2) 導入とデータの確認 冬休みの自由研究： EdgeX Foundry (3) エクスポートサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (4) MQTT デバイスサービスの追加 冬休みの自由研究： EdgeX Foundry (5) ルールエンジンによる自動制御 冬休みの自由研究： EdgeX Foundry (6) アプリケーションサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (7) Kubernetes 上で Fuji リリースを動かす EdgeX Foundry ハンズオンラボガイド公開 EdgeX Foundry： Geneva から Hanoi へ 今回のゴール これまでのエントリでは、仮想デバイスや MQTT デバイスを用いて、デバイスからの情報の収集やデバイスへのコマンドの実行を試してきました。\n今回は、ルールエンジンの動作、つまり、\n何らかのデバイスのリソースの値が 何らかの条件を満たしたら 別のデバイスでコマンドを実行する ような自動制御を実際に試します。\n前回のエントリ で構成した MQTT デバイスだけを利用してもすぐできるのですが、それだけだとおもしろくないので、新しいデバイスサービスをひとつ追加して、それを組み込みます。具体的には、\nルール (1) REST デバイスから送られる値が 80 を越えたら MQTT デバイスにメッセージ HIGH を送信する ルール (2) REST デバイスから送られる値が 20 を下回ったら MQTT デバイスにメッセージ LOW を送信する ような状態を目指します。図示すると以下のような状態です。\n今回も GitHub にもろもろ置いてあります ので、こちらをクローンして使います。\ngit clone https://github.com/kurokobo/edgex-lab-handson.git cd lab04 REST デバイスの追加 まだ開発途中で正式リリースには至っていないようですが、device-rest-go と名付けられたデバイスサービスがあります。\nREST デバイスサービスの概要 これは、以下のようなデバイスサービスです。\nREST で POST してくるデバイスに利用する POST されたリクエストボディの中身を値として EdgeX Foundry に取り込む このデバイスサービスを構成すると、エンドポイントとして\n/api/v1/resource/\u0026lt;デバイス名\u0026gt;/\u0026lt;リソース名\u0026gt; が作成され、これに対して POST することで値を取り込んでくれるようになります。デバイス名やリソース名は、前回取り扱った MQTT デバイスと同様、デバイスプロファイルなどの設定ファイルで定義できます。\nなお、現時点では情報のやりとりは一方向であり、つまり、デバイス側から POST された値を取り込むのみで、逆にデバイス側へのリクエストの発行はできないようです。 また、JSON を投げても、現時点の実装ではパースはしてくれず単なる文字列として扱われるようです。\n今回試す設計 今回は、 device-rest-go の README.md を参考に、以下のデバイスサービスとデバイスを定義しています。\nデバイスプロファイル rest/rest.test.device.profile.yml ファイル リソース int と float を定義 デバイスサービス設定 rest/configuration.toml ファイル 上記のデバイスプロファイルに紐づけた REST_DEVICE を定義 これにより、\n/api/v1/resource/REST_DEVICE/int /api/v1/resource/REST_DEVICE/float に値を POST すれば取り込んでもらえるようになるはずです。\nこの REST デバイスサービスを含め、今回分の環境は、Docker Compose ファイルに反映済みです\n起動 では、今回分の環境をまとめて起動させます。\nMQTT デバイスは今回も使うので、 環境に合わせて mqtt/configuration.toml は修正してください。\nMQTT ブローカ、MQTT デバイス、EdgeX Foundry の順に起動します。\ndocker run -d --rm --name broker -p 1883:1883 eclipse-mosquitto docker run -d --restart=always --name=mqtt-scripts -v \u0026#34;$(pwd)/mqtt-scripts:/scripts\u0026#34; dersimn/mqtt-scripts --url mqtt://192.168.0.100 --dir /scripts docker-compose up -d REST デバイスサービスの動作確認 起動が確認できたら、実際にエンドポイントに POST して、データの取り込みを確認します。待ち受けポートは 49986 です。Content-Type は text/plain である必要があります。\ncurl -X POST -H \u0026#34;Content-Type: text/plain\u0026#34; -d 12345 http://localhost:49986/api/v1/resource/REST_DEVICE/int edgex-device-rest コンテナのログで Pushed event to core data が記録されていれば、値は正常に受け付けられています。\n$ docker logs --tail 5 edgex-device-rest level=DEBUG ts=2020-01-25T07:49:17.0621062Z app=edgex-device-rest source=resthandler.go:82 msg=\u0026#34;Received POST for Device=REST_DEVICE Resource=int\u0026#34; level=DEBUG ts=2020-01-25T07:49:17.0673842Z app=edgex-device-rest source=resthandler.go:101 msg=\u0026#34;Content Type is \u0026#39;text/plain\u0026#39; \u0026amp; Media Type is \u0026#39;text/plain\u0026#39; and Type is \u0026#39;Int64\u0026#39;\u0026#34; level=DEBUG ts=2020-01-25T07:49:17.0726606Z app=edgex-device-rest source=resthandler.go:142 msg=\u0026#34;Incoming reading received: Device=REST_DEVICE Resource=int\u0026#34; level=DEBUG ts=2020-01-25T07:49:17.0766349Z app=edgex-device-rest source=utils.go:75 correlation-id=946f482c-db1f-44b5-8a76-437c4ca3d3e9 msg=\u0026#34;SendEvent: EventClient.MarshalEvent encoded event\u0026#34; level=INFO ts=2020-01-25T07:49:17.0899243Z app=edgex-device-rest source=utils.go:93 Content-Type=application/json correlation-id=946f482c-db1f-44b5-8a76-437c4ca3d3e9 msg=\u0026#34;SendEvent: Pushed event to core data\u0026#34; 実際に取り込まれているようです。\n$ edgex-cli reading list REST_DEVICE -l 10 Reading ID Name Device Origin Value Created Modified Pushed b43f7300-1377-4fdb-a82f-44eb497d9568 int REST_DEVICE 1579938557072646900 12345 3 minutes 3 minutes 50 years ルールエンジンの利用 お膳立てができたので、本題のルールエンジンを構成していきます。\nルールエンジンの概要 詳細は 公式のドキュメント がわかりやすいですが、端的に言えば、\nコアサービスまたはエクスポートサービスからデータを受け取る 事前に定義されたルールの条件との合致を確認する 合致していた場合は、そのルールで定義されたアクションを実行する ような処理をしてくれるサービスです。\n実装は現段階では BRMS の Drools ベースとのことなので、Java 製ですね。\nルールエンジンへデータを配信する構成パタンは、ドキュメントでは以下の二つが説明されています。\nエクスポートサービスのクライアントとして動作させるパタン データは Export Distribution（edgex-export-distro コンテナ）から配信される データの流れが全体で統一されてシンプルに レイテンシやエクスポートサービスのキャパシティなどの面にデメリット コアサービスに直結させるパタン データは Core Data（edgex-core-data コンテナ）から配信される エクスポートサービスをバイパスすることで、レイテンシやパフォーマンス面ではメリットがある データの流れはやや複雑に この構成パタンの選択や、具体的な接続先の情報は、次で紹介する設定ファイルで指定できます。\nルールエンジンの設定 設定がデフォルトのままでよければ、コンテナイメージの中にすべて含まれているので、敢えて手で投入する必要はありませんが、今回はせっかくなので触れるようにしています。rulesengine フォルダの中のファイル群がそれです。\n公式のドキュメント で触れられている設定が含まれるファイルが、rulesengine/application.properties です。\n$ cat rulesengine/application.properties ... export.client=false ... export.client.registration.url=http://edgex-export-client:48071/api/v1 export.client.registration.name=EdgeXRulesEngine ... export.zeromq.port=5563 export.zeromq.host=tcp://edgex-core-data ... export.client が false なので、今回はコアサービスから直接データを受け取ります。export.zeromq.host は現時点のデフォルトでは tcp://edgex-app-service-configurable-rules になっており、アプリケーションサービス からデータを受け取る形になっていますが、今回はシンプルにドキュメントに従って tcp://edgex-core-data にしています。\nもう一つのファイルが、rulesengine/rule-template.drl です。これはルールそのもののテンプレートとして利用されます。これについては後述します。\nルールの設定 では、実際にルールを設定していきます。現状、GUI でも CLI でもできないので、API を叩きます。標準 GUI には設定画面はあるものの、プルダウンに項目が出てこなくて設定できずでした。\nルールの検討 ルールは以下ような JSON で投入します。\n{ \u0026#34;name\u0026#34;: \u0026#34;\u0026lt;ルール名\u0026gt;\u0026#34;, \u0026#34;condition\u0026#34;: { \u0026#34;device\u0026#34;: \u0026#34;\u0026lt;トリガ条件にするデバイス名\u0026gt;\u0026#34;, \u0026#34;checks\u0026#34;: [ { \u0026#34;parameter\u0026#34;: \u0026#34;\u0026lt;トリガ条件にするリソース名\u0026gt;\u0026#34;, \u0026#34;operand1\u0026#34;: \u0026#34;\u0026lt;トリガ条件にする値\u0026gt;\u0026#34;, \u0026#34;operation\u0026#34;: \u0026#34;\u0026lt;比較演算子\u0026gt;\u0026#34;, \u0026#34;operand2\u0026#34;: \u0026#34;\u0026lt;トリガする閾値\u0026gt;\u0026#34; } ] }, \u0026#34;action\u0026#34;: { \u0026#34;device\u0026#34;: \u0026#34;\u0026lt;操作対象のデバイス ID\u0026gt;\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;\u0026lt;操作対象のコマンド ID\u0026gt;\u0026#34;, \u0026#34;body\u0026#34;: \u0026#34;\u0026lt;操作対象のコマンドに PUT される内容\u0026gt;\u0026#34; }, \u0026#34;log\u0026#34;: \u0026#34;\u0026lt;トリガされたときのログ出力文字列\u0026gt;\u0026#34; } 全体の構造は、condition で指定した条件を満たしたら action で指定したコマンドがトリガされる、と思えばよいでしょう。\ncondition での指定値は以下のように組んでいきます。\ndevice トリガ条件にするデバイスの名称を指定します。ID では動かないので注意です 今回だと、REST デバイスの値を元にコマンドを実行したいので、REST_DEVICE を指定します parameter トリガ条件にするデバイスのリソース名を指定します。リソース名なので、デバイスプロファイルで指定した名称であり、すなわち Reading の名前でもあります 今回の REST_DEVICE はリソース int と float を持ちますが、今回は int を指定します operand1 比較元になる値を作ります。デバイスの値は内部では文字列値として扱われているため、数値であれば適切な型にキャストする必要があります Drools が Java ベースのため、ここでは Java の文法で書きます。値に応じて、例えば以下などが使い分けられるでしょう Integer.parseInt(value) Float.parseFloat(value) operation 比較演算子です。\u0026lt;、=、\u0026gt; などが考えられます operand2 閾値です action での指定値は、以下のように組みます。\ndevice 先ほどと異なり、ここでは ID で指定します 今回は MQ_DEVICE の ID です デバイスの ID は API で確認できます（方法は 過去のエントリ で） command コマンドも ID で指定します 今回は testmessage の ID です コマンドの ID も API で確認できます（方法は 過去のエントリ で） body コマンドの PUT 命令のボディを JSON 形式で指定します めっちゃエスケープが必要です 例えば今回だと、{\\\\\\\u0026quot;message\\\\\\\u0026quot;:\\\\\\\u0026quot;HIGH\\\\\\\u0026quot;} や {\\\\\\\u0026quot;message\\\\\\\u0026quot;:\\\\\\\u0026quot;LOW\\\\\\\u0026quot;} です 最終的に、今回作りたい以下のルール群は、\nルール (1) REST デバイスから送られる値が 80 を越えたら MQTT デバイスにメッセージ HIGH を送信する ルール (2) REST デバイスから送られる値が 20 を下回ったら MQTT デバイスにメッセージ LOW を送信する ひとつめは以下の JSON で、\n{ \u0026#34;name\u0026#34;: \u0026#34;rule_int_high\u0026#34;, \u0026#34;condition\u0026#34;: { \u0026#34;device\u0026#34;: \u0026#34;REST_DEVICE\u0026#34;, \u0026#34;checks\u0026#34;: [ { \u0026#34;parameter\u0026#34;: \u0026#34;int\u0026#34;, \u0026#34;operand1\u0026#34;: \u0026#34;Integer.parseInt(value)\u0026#34;, \u0026#34;operation\u0026#34;: \u0026#34;\u0026gt;\u0026#34;, \u0026#34;operand2\u0026#34;: \u0026#34;80\u0026#34; } ] }, \u0026#34;action\u0026#34;: { \u0026#34;device\u0026#34;: \u0026#34;09dae1fc-e2be-4388-9677-639d2f24c58b\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;1c7a50e7-5424-4bce-9b9a-29849510580e\u0026#34;, \u0026#34;body\u0026#34;: \u0026#34;{\\\\\\\u0026#34;message\\\\\\\u0026#34;:\\\\\\\u0026#34;HIGH\\\\\\\u0026#34;}\u0026#34; }, \u0026#34;log\u0026#34;: \u0026#34;Action triggered: The value is too high.\u0026#34; } ふたつめは以下の JSON であらわされます。\n{ \u0026#34;name\u0026#34;: \u0026#34;rule_int_low\u0026#34;, \u0026#34;condition\u0026#34;: { \u0026#34;device\u0026#34;: \u0026#34;REST_DEVICE\u0026#34;, \u0026#34;checks\u0026#34;: [ { \u0026#34;parameter\u0026#34;: \u0026#34;int\u0026#34;, \u0026#34;operand1\u0026#34;: \u0026#34;Integer.parseInt(value)\u0026#34;, \u0026#34;operation\u0026#34;: \u0026#34;\u0026lt;\u0026#34;, \u0026#34;operand2\u0026#34;: \u0026#34;20\u0026#34; } ] }, \u0026#34;action\u0026#34;: { \u0026#34;device\u0026#34;: \u0026#34;09dae1fc-e2be-4388-9677-639d2f24c58b\u0026#34;, \u0026#34;command\u0026#34;: \u0026#34;1c7a50e7-5424-4bce-9b9a-29849510580e\u0026#34;, \u0026#34;body\u0026#34;: \u0026#34;{\\\\\\\u0026#34;message\\\\\\\u0026#34;:\\\\\\\u0026#34;LOW\\\\\\\u0026#34;}\u0026#34; }, \u0026#34;log\u0026#34;: \u0026#34;Action triggered: The value is too low.\u0026#34; } ルールの投入 では、ルールを投入します。エンドポイントは以下です。\nhttp://localhost:48075/api/v1/rule ここに、先の JSON を POST します。\nルールの確認 ルールが投入されると、rule-template.drl を元に新しい \u0026lt;ルール名\u0026gt;.drl ファイルが生成されて利用されます。edgex-support-rulesengine 内に配置されるので、ここではコンテナ内のファイルを cat して覗きます。\n$ docker exec edgex-support-rulesengine cat /edgex/edgex-support-rulesengine/rules/rule_int_high.drl package org.edgexfoundry.rules; global org.edgexfoundry.engine.CommandExecutor executor; global org.edgexfoundry.support.logging.client.EdgeXLogger logger; import org.edgexfoundry.domain.core.Event; import org.edgexfoundry.domain.core.Reading; import java.util.Map; rule \u0026#34;rule_int_high\u0026#34; when $e:Event($rlist: readings \u0026amp;\u0026amp; device==\u0026#34;REST_DEVICE\u0026#34;) $r0:Reading(name==\u0026#34;int\u0026#34; \u0026amp;\u0026amp; Integer.parseInt(value) \u0026gt; 80) from $rlist then executor.fireCommand(\u0026#34;09dae1fc-e2be-4388-9677-639d2f24c58b\u0026#34;, \u0026#34;1c7a50e7-5424-4bce-9b9a-29849510580e\u0026#34;, \u0026#34;{\\\u0026#34;message\\\u0026#34;:\\\u0026#34;HIGH\\\u0026#34;}\u0026#34;); logger.info(\u0026#34;Action triggered: The value is too high.\u0026#34;); end $ docker exec edgex-support-rulesengine cat /edgex/edgex-support-rulesengine/rules/rule_int_low.drl package org.edgexfoundry.rules; global org.edgexfoundry.engine.CommandExecutor executor; global org.edgexfoundry.support.logging.client.EdgeXLogger logger; import org.edgexfoundry.domain.core.Event; import org.edgexfoundry.domain.core.Reading; import java.util.Map; rule \u0026#34;rule_int_low\u0026#34; when $e:Event($rlist: readings \u0026amp;\u0026amp; device==\u0026#34;REST_DEVICE\u0026#34;) $r0:Reading(name==\u0026#34;int\u0026#34; \u0026amp;\u0026amp; Integer.parseInt(value) \u0026lt; 20) from $rlist then executor.fireCommand(\u0026#34;09dae1fc-e2be-4388-9677-639d2f24c58b\u0026#34;, \u0026#34;1c7a50e7-5424-4bce-9b9a-29849510580e\u0026#34;, \u0026#34;{\\\u0026#34;message\\\u0026#34;:\\\u0026#34;LOW\\\u0026#34;}\u0026#34;); logger.info(\u0026#34;Action triggered: The value is too low.\u0026#34;); end 手元の rule-template.drl と見比べると、テンプレートを元に展開されているっぽさがわかりますね。Drools の drl ファイルの文法にあまり詳しくないですが、行われているのが単純な文字列連結なのであれば、operand に指定する値あたりは工夫すると、もうちょっと複雑な計算もできるのかもしれません。試していませんし、インジェクション攻撃っぽいですけど。\nなお、現在の API ではルールの中身までは確認できず、ルール名の一覧が取得できるのみのようです。悲しい。\n$ curl -s http://localhost:48075/api/v1/rule | jq [ \u0026#34;rule_int_high\u0026#34;, \u0026#34;rule_int_low\u0026#34; ] ルールエンジンの動作確認 動作を確認していきます。\n動きを追いやすくするため、下準備として edgex-support-rulesengine のログを常時表示させて、さらに別のターミナルで MQTT ブローカの全トピックを購読しておくとわかりやすいです。\n$ docker logs -f --tail=10 edgex-support-rulesengine [2020-01-25 13:44:15.023] boot - 6 INFO [main] --- ZeroMQEventSubscriber: JSON event received [2020-01-25 13:44:15.024] boot - 6 INFO [main] --- ZeroMQEventSubscriber: Event sent to rules engine for device id: MQ_DEVICE [2020-01-25 13:44:17.214] boot - 6 INFO [main] --- ZeroMQEventSubscriber: JSON event received [2020-01-25 13:44:17.216] boot - 6 INFO [main] --- ZeroMQEventSubscriber: Event sent to rules engine for device id: MQ_DEVICE ... $ docker run --init --rm -it efrecon/mqtt-client sub -h 192.168.0.100 -t \u0026#34;#\u0026#34; -v logic/connected 2 DataTopic {\u0026#34;name\u0026#34;:\u0026#34;MQ_DEVICE\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;randfloat32\u0026#34;,\u0026#34;randfloat32\u0026#34;:\u0026#34;27.0\u0026#34;} ... では、REST デバイスの気持ちになって、まずはルールに合致しないデータを REST デバイスサービスに POST します。\ncurl -X POST -H \u0026#34;Content-Type: text/plain\u0026#34; -d 50 http://localhost:49986/api/v1/resource/REST_DEVICE/int 値は取り込まれていますし、\n$ edgex-cli reading list REST_DEVICE -l 10 Reading ID Name Device Origin Value Created Modified Pushed 66461804-a894-470f-a099-631ffd4c32cb int REST_DEVICE 1579960045357298000 50 About a minute About a minute 50 years ルールエンジンにも REST_DEVICE からの値は届いているようなログが出ますが、実際には何もトリガされません。正常です。\n$ docker logs -f --tail=10 edgex-support-rulesengine ... [2020-01-25 13:47:25.370] boot - 6 INFO [main] --- ZeroMQEventSubscriber: JSON event received [2020-01-25 13:47:25.466] boot - 6 INFO [main] --- ZeroMQEventSubscriber: Event sent to rules engine for device id: REST_DEVICE ... つづいて、ルールに合致する値を投げます。\ncurl -X POST -H \u0026#34;Content-Type: text/plain\u0026#34; -d 90 http://192.168.0.100:49986/api/v1/resource/REST_DEVICE/int ルールエンジンのログでは、指定したログメッセージが記録され、 {\u0026quot;message\u0026quot;:\u0026quot;HIGH\u0026quot;} が指定したコマンドにリクエストされたことがわかります。\n$ docker logs -f --tail=10 edgex-support-rulesengine ... [2020-01-25 13:57:26.805] boot - 6 INFO [main] --- ZeroMQEventSubscriber: JSON event received [2020-01-25 13:57:26.853] boot - 6 INFO [main] --- RuleEngine: Action triggered: The value is too high. [2020-01-25 13:57:26.853] boot - 6 INFO [SimpleAsyncTaskExecutor-2] --- CommandExecutor: Sending request to: 09dae1fc-e2be-4388-9677-639d2f24c58bfor command: 1c7a50e7-5424-4bce-9b9a-29849510580e with body: {\u0026#34;message\u0026#34;:\u0026#34;HIGH\u0026#34;} [2020-01-25 13:57:26.866] boot - 6 INFO [main] --- RuleEngine: Event triggered 1rules: Event [pushed=0, device=REST_DEVICE, readings=[Reading [pushed=0, name=int, value=90, device=REST_DEVICE]], toString()=BaseObject [id=538aea34-b4ce-4342-88a9-6c08cdac080e, created=0, modified=0, origin=1579960646793054700]] ... MQTT ブローカ上では、MQTT デバイスに対するコマンド実行が行われた様子がわかります。\n$ docker run --init --rm -it efrecon/mqtt-client sub -h 192.168.0.100 -t \u0026#34;#\u0026#34; -v CommandTopic {\u0026#34;cmd\u0026#34;:\u0026#34;message\u0026#34;,\u0026#34;message\u0026#34;:\u0026#34;HIGH\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;set\u0026#34;,\u0026#34;uuid\u0026#34;:\u0026#34;5e2c4946b8dd790001754b8b\u0026#34;} ResponseTopic {\u0026#34;cmd\u0026#34;:\u0026#34;message\u0026#34;,\u0026#34;message\u0026#34;:\u0026#34;HIGH\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;set\u0026#34;,\u0026#34;uuid\u0026#34;:\u0026#34;5e2c4946b8dd790001754b8b\u0026#34;} ... MQTT デバイスの testmessage コマンドに GET すると、値が狙い通りに変更されていることがわかります。\n$ curl -s http://localhost:48082/api/v1/device/name/MQ_DEVICE/command/testmessage | jq { \u0026#34;device\u0026#34;: \u0026#34;MQ_DEVICE\u0026#34;, \u0026#34;origin\u0026#34;: 1579960983817888000, \u0026#34;readings\u0026#34;: [ { \u0026#34;origin\u0026#34;: 1579960983809258000, \u0026#34;device\u0026#34;: \u0026#34;MQ_DEVICE\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;message\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;HIGH\u0026#34; } ], \u0026#34;EncodedEvent\u0026#34;: null } 同様に、REST デバイスの気持ちになってふたつめのルールに合致する値を投げます。\ncurl -X POST -H \u0026#34;Content-Type: text/plain\u0026#34; -d 10 http://192.168.0.100:49986/api/v1/resource/REST_DEVICE/int もろもろの処理が動き、MQTT デバイス側の値が変わったことが確認できます。\n$ curl -s http://localhost:48082/api/v1/device/name/MQ_DEVICE/command/testmessage | jq { \u0026#34;device\u0026#34;: \u0026#34;MQ_DEVICE\u0026#34;, \u0026#34;origin\u0026#34;: 1579961107845061000, \u0026#34;readings\u0026#34;: [ { \u0026#34;origin\u0026#34;: 1579961107837083000, \u0026#34;device\u0026#34;: \u0026#34;MQ_DEVICE\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;message\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;LOW\u0026#34; } ], \u0026#34;EncodedEvent\u0026#34;: null } まとめ あるデバイスの値の変化をトリガにして、別のデバイスを制御できることが確認できました。\n今回は、REST デバイスを使った関係で、実質手動でトリガさせたに等しい状況でしたが、本来はセンサの値をトリガにアクチュエータを動かすような使い方になるでしょう。REST デバイスの代わりに MQTT デバイスや仮想デバイスのランダム値を condition に指定すれば、似たような状況のテストが可能です。\n現段階ではシンプルなルールエンジンしか積まれていませんが、今後エコシステムが成熟してアプリケーションサービスなどが充実してくれば、より複雑な処理も可能になることが期待できます。\nEdgeX Foundry 関連エントリ 冬休みの自由研究： EdgeX Foundry (1) 概要 冬休みの自由研究： EdgeX Foundry (2) 導入とデータの確認 冬休みの自由研究： EdgeX Foundry (3) エクスポートサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (4) MQTT デバイスサービスの追加 冬休みの自由研究： EdgeX Foundry (5) ルールエンジンによる自動制御 冬休みの自由研究： EdgeX Foundry (6) アプリケーションサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (7) Kubernetes 上で Fuji リリースを動かす EdgeX Foundry ハンズオンラボガイド公開 EdgeX Foundry： Geneva から Hanoi へ ","date":"2020-01-25T14:45:02Z","image":"/archives/2760/img/DSC06315.jpg","permalink":"/archives/2760/","title":"冬休みの自由研究： EdgeX Foundry (5) ルールエンジンによる自動制御"},{"content":"EdgeX Foundry 関連エントリ 冬休みの自由研究： EdgeX Foundry (1) 概要 冬休みの自由研究： EdgeX Foundry (2) 導入とデータの確認 冬休みの自由研究： EdgeX Foundry (3) エクスポートサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (4) MQTT デバイスサービスの追加 冬休みの自由研究： EdgeX Foundry (5) ルールエンジンによる自動制御 冬休みの自由研究： EdgeX Foundry (6) アプリケーションサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (7) Kubernetes 上で Fuji リリースを動かす EdgeX Foundry ハンズオンラボガイド公開 EdgeX Foundry： Geneva から Hanoi へ おさらい 前々回のエントリ では、下図のとおり、バンドルされている仮想デバイスを利用して動作を確認していました。\nこれらの仮想デバイスは、デバイスからのデータの受け取りやデバイスへのコマンド実行などをテストする目的ではたいへん便利ですが、現実世界とのインタラクションはできません。\nそこで今回は、MQTT をインタフェイスにもつデバイス（のシミュレータ）を用意し、下図のように EdgeX Foundry がそのデバイスと実際に（MQTT ブローカを介して）インタラクションできる状態を構成します。\n本エントリの作業内容は、エントリ執筆時点の 公式ドキュメント を基にしています。\n今回も、GitHub にもろもろ置いてあります ので、こちらをクローンして使います。\ngit clone https://github.com/kurokobo/edgex-lab-handson.git cd lab03 新しいデバイスの仕様と動作確認 まずは、今回新しく EdgeX Foundry の制御下におきたいデバイスの仕様を整理します。その後、実際にそのデバイスのシミュレータを動作させ、仕様通りに動くことを確認します。\n新しいデバイスの仕様 今回は、以下のような仕様のデバイスを考えます。\n二種類のセンサを持つ randfloat32 randfloat64 二種類の文字列情報を持つ ping message トピック DataTopic にセンサ randfloat32 の値を 15 秒ごとに配信する {\u0026quot;name\u0026quot;:\u0026quot;MQ_DEVICE\u0026quot;,\u0026quot;cmd\u0026quot;:\u0026quot;randfloat32\u0026quot;,\u0026quot;randfloat32\u0026quot;:\u0026quot;\u0026lt;値\u0026gt;\u0026quot;} の形式で配信する トピック CommandTopic でコマンドを待ち受ける {\u0026quot;name\u0026quot;:\u0026quot;MQTT_DEVICE\u0026quot;,\u0026quot;method\u0026quot;:\u0026quot;\u0026lt;get または set\u0026gt;\u0026quot;,\u0026quot;cmd\u0026quot;:\u0026quot;\u0026lt;コマンド名\u0026gt;\u0026quot;} の形式のコマンドを受け取る コマンドを受け取ったら、コマンド応じて処理を実行し、結果をトピック ResponseTopic に配信する コマンドのメソッドが set だった場合は、保存されている message を書き換える コマンドが ping だった場合は、pong を返す コマンドが message だった場合は、保存されている message を返す コマンドが randfloat32 または randfloat64 だった場合は、それぞれ対応するセンサの値を返す 結果は {\u0026quot;name\u0026quot;:\u0026quot;MQ_DEVICE\u0026quot;,\u0026quot;method\u0026quot;:\u0026quot;\u0026lt;メソッド\u0026gt;\u0026quot;,\u0026quot;cmd\u0026quot;:\u0026quot;\u0026lt;コマンド名\u0026gt;\u0026quot;,\u0026quot;\u0026lt;コマンド名\u0026gt;\u0026quot;:\u0026quot;\u0026lt;値\u0026gt;\u0026quot;} の形式で配信する デバイス側が定期的にデータを発信するだけでなく、コマンド操作も受け付けるようなデバイスです。ただしよく見ると、二つあるセンサのうち randfloat64 は、値の定期的な自動配信はありません（これは伏線です）。\nシミュレータの起動 今回は、上記のようなデバイスを模したシミュレータを利用します。MQTT を扱うにはブローカが不可欠なので、前回のエントリ と同様にまずはこれを起動します。\n$ docker run -d --rm --name broker -p 1883:1883 eclipse-mosquitto 6de5986ebb1b3715179253857952f143fbf19fa77e201980f8573d96558850fd 続けてデバイス（のシミュレータ）の用意です。\nNode.js で MQTT を扱えるコンテナ dersimn/mqtt-scripts で、次のようなスクリプトを起動させます。\nfunction getRandomFloat(min, max) { return Math.random() * (max - min) + min; } const deviceName = \u0026#34;MQ_DEVICE\u0026#34;; let message = \u0026#34;test-message\u0026#34;; // 1. Publish random number every 15 seconds schedule(\u0026#39;*/15 * * * * *\u0026#39;, () =\u0026gt; { let body = { \u0026#34;name\u0026#34;: deviceName, \u0026#34;cmd\u0026#34;: \u0026#34;randfloat32\u0026#34;, \u0026#34;randfloat32\u0026#34;: getRandomFloat(25, 29).toFixed(1) }; publish(\u0026#39;DataTopic\u0026#39;, JSON.stringify(body)); }); // 2. Receive the reading request, then return the response // 3. Receive the put request, then change the device value subscribe(\u0026#34;CommandTopic\u0026#34;, (topic, val) =\u0026gt; { var data = val; if (data.method == \u0026#34;set\u0026#34;) { message = data[data.cmd] } else { switch (data.cmd) { case \u0026#34;ping\u0026#34;: data.ping = \u0026#34;pong\u0026#34;; break; case \u0026#34;message\u0026#34;: data.message = message; break; case \u0026#34;randfloat32\u0026#34;: data.randfloat32 = getRandomFloat(25, 29).toFixed(1); break; case \u0026#34;randfloat64\u0026#34;: data.randfloat64 = getRandomFloat(10, 1).toFixed(5); break; } } publish(\u0026#34;ResponseTopic\u0026#34;, JSON.stringify(data)); }); これを起動するには、以下のように作業します。IP アドレスは適宜読み替えてください。\n$ cd mqtt-scripts $ docker run -d --restart=always --name=mqtt-scripts -v \u0026#34;$(pwd):/scripts\u0026#34; dersimn/mqtt-scripts --url mqtt://192.168.0.100 --dir /scripts Unable to find image \u0026#39;dersimn/mqtt-scripts:latest\u0026#39; locally ... 9d52b4956ebbec60dd0f93bdf9750ff915fefd8e8c58d3ce8bc7ba1429693d2b シミュレータの動作確認 ブローカの全トピックを購読します。15 秒ごとに DataTopic にセンサ randfloat32 の値が届いていることがわかります。\n$ docker run --init --rm -it efrecon/mqtt-client sub -h 192.168.0.100 -t \u0026#34;#\u0026#34; -v ... DataTopic {\u0026#34;name\u0026#34;:\u0026#34;MQ_DEVICE\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;randfloat32\u0026#34;,\u0026#34;randfloat32\u0026#34;:\u0026#34;27.4\u0026#34;} DataTopic {\u0026#34;name\u0026#34;:\u0026#34;MQ_DEVICE\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;randfloat32\u0026#34;,\u0026#34;randfloat32\u0026#34;:\u0026#34;28.6\u0026#34;} コマンド操作ができることも確認します。別のターミナルで CommandTopic にコマンドを配信します。Windows 環境だと \u0026quot; を \\\u0026quot; にしないと動かないかもしれません。\ndocker run --init -it --rm efrecon/mqtt-client pub -h 192.168.0.100 -t \u0026#34;CommandTopic\u0026#34; -m \u0026#39;{\u0026#34;name\u0026#34;:\u0026#34;MQTT_DEVICE\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;get\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;ping\u0026#34;}\u0026#39; docker run --init -it --rm efrecon/mqtt-client pub -h 192.168.0.100 -t \u0026#34;CommandTopic\u0026#34; -m \u0026#39;{\u0026#34;name\u0026#34;:\u0026#34;MQTT_DEVICE\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;get\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;randfloat32\u0026#34;}\u0026#39; docker run --init -it --rm efrecon/mqtt-client pub -h 192.168.0.100 -t \u0026#34;CommandTopic\u0026#34; -m \u0026#39;{\u0026#34;name\u0026#34;:\u0026#34;MQTT_DEVICE\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;get\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;randfloat64\u0026#34;}\u0026#39; docker run --init -it --rm efrecon/mqtt-client pub -h 192.168.0.100 -t \u0026#34;CommandTopic\u0026#34; -m \u0026#39;{\u0026#34;name\u0026#34;:\u0026#34;MQTT_DEVICE\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;get\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;message\u0026#34;}\u0026#39; docker run --init -it --rm efrecon/mqtt-client pub -h 192.168.0.100 -t \u0026#34;CommandTopic\u0026#34; -m \u0026#39;{\u0026#34;name\u0026#34;:\u0026#34;MQTT_DEVICE\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;set\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;message\u0026#34;,\u0026#34;message\u0026#34;:\u0026#34;modified-message\u0026#34;}\u0026#39; docker run --init -it --rm efrecon/mqtt-client pub -h 192.168.0.100 -t \u0026#34;CommandTopic\u0026#34; -m \u0026#39;{\u0026#34;name\u0026#34;:\u0026#34;MQTT_DEVICE\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;get\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;message\u0026#34;}\u0026#39; 購読している側では、コマンドが CommandTopic に届き、そのコマンドに応じた応答が ResponseTopic に配信されていることが確認できます。また、message コマンドの結果が、set メソッドの実行前後で変更されていることも確認できます。\n$ docker run --init --rm -it efrecon/mqtt-client sub -h 192.168.0.100 -t \u0026#34;#\u0026#34; -v ... CommandTopic {\u0026#34;name\u0026#34;:\u0026#34;MQTT_DEVICE\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;get\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;ping\u0026#34;} ResponseTopic {\u0026#34;name\u0026#34;:\u0026#34;MQTT_DEVICE\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;get\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;ping\u0026#34;,\u0026#34;ping\u0026#34;:\u0026#34;pong\u0026#34;} ... CommandTopic {\u0026#34;name\u0026#34;:\u0026#34;MQTT_DEVICE\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;get\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;randfloat32\u0026#34;} ResponseTopic {\u0026#34;name\u0026#34;:\u0026#34;MQTT_DEVICE\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;get\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;randfloat32\u0026#34;,\u0026#34;randfloat32\u0026#34;:\u0026#34;27.6\u0026#34;} ... CommandTopic {\u0026#34;name\u0026#34;:\u0026#34;MQTT_DEVICE\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;get\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;randfloat64\u0026#34;} ResponseTopic {\u0026#34;name\u0026#34;:\u0026#34;MQTT_DEVICE\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;get\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;randfloat64\u0026#34;,\u0026#34;randfloat64\u0026#34;:\u0026#34;8.39883\u0026#34;} ... CommandTopic {\u0026#34;name\u0026#34;:\u0026#34;MQTT_DEVICE\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;get\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;message\u0026#34;} ResponseTopic {\u0026#34;name\u0026#34;:\u0026#34;MQTT_DEVICE\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;get\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;message\u0026#34;,\u0026#34;message\u0026#34;:\u0026#34;test-message\u0026#34;} ... CommandTopic {\u0026#34;name\u0026#34;:\u0026#34;MQTT_DEVICE\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;set\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;message\u0026#34;,\u0026#34;message\u0026#34;:\u0026#34;modified-message\u0026#34;} ResponseTopic {\u0026#34;name\u0026#34;:\u0026#34;MQTT_DEVICE\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;set\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;message\u0026#34;,\u0026#34;message\u0026#34;:\u0026#34;modified-message\u0026#34;} ... CommandTopic {\u0026#34;name\u0026#34;:\u0026#34;MQTT_DEVICE\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;get\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;message\u0026#34;} ResponseTopic {\u0026#34;name\u0026#34;:\u0026#34;MQTT_DEVICE\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;get\u0026#34;,\u0026#34;cmd\u0026#34;:\u0026#34;message\u0026#34;,\u0026#34;message\u0026#34;:\u0026#34;modified-message\u0026#34;} ここまでで、デバイス（のシミュレータ）が動作している状態が作れました。ここまでは EdgeX Foundry はまったく関係なく、ただ単に前述の仕様のデバイスを論理的に作っただけです。\nデバイスサービスの構成 さて、ここからが本題です。先ほど作ったデバイスを EdgeX Foundry の制御下におくことを考えます。\nデバイスサービスの概念 現実世界に存在するデバイスは、それぞれが異なるセンサやスイッチを持っていて、異なるプロトコルをインタフェイスに持つため、それぞれに応じた適切な方法で値の取得や制御命令の発行をする必要があります。\nこの実態を踏まえ、EdgeX Foundry では、実デバイスと EdgeX Foundry の間を取り持ってくれる存在としてデバイスサービスとよばれるマイクロサービスを定義しています。デバイスサービスは、おおむねデバイスの種類ごとに用意するものと思えばよさそうで、つまり、ひとつのデバイスサービスに対してひとつ以上のデバイスの制御を任せられるようです。\nデバイスサービスは、以下のような役割を持ちます。\nコアサービス層に対して、デバイスからの情報取得やデバイスへの制御命令の発行を行える REST エンドポイントを提供する コアサービス層からの制御命令を実デバイスに合わせた命令に翻訳して実行し、実デバイスを制御する デバイスから送られてきた情報をコアサービス層に合わせた情報に翻訳し、コアサービス層に届ける 事前に定められた場合は、定期的にデバイスに対して特定の処理を行い、結果をコアサービス層に届ける また、デバイスサービスそのものは、以下のような特徴を持ちます。\nMQTT や Modbus など業界で多く使われるプロトコルに合わせたデバイスサービスはすでに参考実装が用意されており、複雑でない構成の場合は充分に流用できる 参考実装の流用では不足する場合は、提供されている SDK を利用して気軽に自製できる（C 言語または Go 言語） すべてのデバイスサービスはこの SDK を用いて共通のフレームワークのもとで実装されるため、操作方法や設定方法は自ずと共通化され、相互運用性は高まる EdgeX Foundry を取り巻くエコシステムの成熟により、デバイスのベンダがそのデバイス用のデバイスサービスを提供したり、デバイスにデバイスサービスが組み込まれたり、第三者により開発されたデバイスサービスの充実も期待できる EdgeX Foundry に限った話ではないですが、プラットフォーム系の製品の場合、製品の発展にはエコシステムの成熟が非常に重要です。この観点では、EdgeX Foundry は、商用環境への利用を謳ったバージョン 1.0 のリリースが 2019 年の冬とついこの前なので、まだまだこれからですね。\nデバイスサービスの構成要素 デバイスを制御するデバイスサービスを追加するには、以下の 3 つの要素を考える必要があります。\nデバイスサービスそのもの コアサービスとデバイスとの間を仲介する、C 言語または Go 言語で実装されたマイクロサービス 今回はコンテナとして動作させる 後述のデバイスプロファイルとデバイスサービス設定を読み込んで動作する デバイスプロファイル（Device Profile） YAML ファイルとして定義 管理対象のデバイス種別が持つリソースや、そのリソースが持つ値の意味、それぞれのリソースに対して行える操作などを定義する デバイスサービス設定（Drvice Service Configuration） configuration.toml ファイルとして定義 デバイスサービス自身の設定や、連携する EdgeX Foundry の他のマイクロサービスの情報などを定義する デバイスプロファイルと実際のデバイスを紐づけてデバイスを定義する。デバイスやリソースに対する自動実行処理なども定義する その他、デバイスサービスの動作に必要なパラメータを指定する デバイスプロファイル 今回は、mqtt.test.device.profile.yml を利用します。中身は以下の通りです。\n$ cat mqtt/mqtt.test.device.profile.yml name: \u0026#34;Test.Device.MQTT.Profile\u0026#34; manufacturer: \u0026#34;Dell\u0026#34; model: \u0026#34;MQTT-2\u0026#34; labels: - \u0026#34;test\u0026#34; description: \u0026#34;Test device profile\u0026#34; deviceResources: - name: randfloat32 description: \u0026#34;device random number with Base64 encoding\u0026#34; properties: value: { type: \u0026#34;Float32\u0026#34;, size: \u0026#34;4\u0026#34;, readWrite: \u0026#34;R\u0026#34;, defaultValue: \u0026#34;0.00\u0026#34;, minimum: \u0026#34;100.00\u0026#34;, maximum: \u0026#34;0.00\u0026#34;, floatEncoding: \u0026#34;Base64\u0026#34; } units: { type: \u0026#34;String\u0026#34;, readWrite: \u0026#34;R\u0026#34;, defaultValue: \u0026#34;\u0026#34; } - name: randfloat64 description: \u0026#34;device random number with e notion\u0026#34; properties: value: { type: \u0026#34;Float64\u0026#34;, size: \u0026#34;4\u0026#34;, readWrite: \u0026#34;R\u0026#34;, defaultValue: \u0026#34;0.00\u0026#34;, minimum: \u0026#34;100.00\u0026#34;, maximum: \u0026#34;0.00\u0026#34;, floatEncoding: \u0026#34;eNotation\u0026#34; } units: { type: \u0026#34;String\u0026#34;, readWrite: \u0026#34;R\u0026#34;, defaultValue: \u0026#34;\u0026#34; } - name: ping description: \u0026#34;device awake\u0026#34; properties: value: { type: \u0026#34;String\u0026#34;, size: \u0026#34;0\u0026#34;, readWrite: \u0026#34;R\u0026#34;, defaultValue: \u0026#34;oops\u0026#34; } units: { type: \u0026#34;String\u0026#34;, readWrite: \u0026#34;R\u0026#34;, defaultValue: \u0026#34;\u0026#34; } - name: message description: \u0026#34;device notification message\u0026#34; properties: value: { type: \u0026#34;String\u0026#34;, size: \u0026#34;0\u0026#34;, readWrite: \u0026#34;W\u0026#34; ,scale: \u0026#34;\u0026#34;, offset: \u0026#34;\u0026#34;, base: \u0026#34;\u0026#34; } units: { type: \u0026#34;String\u0026#34;, readWrite: \u0026#34;R\u0026#34;, defaultValue: \u0026#34;\u0026#34; } deviceCommands: - name: testrandfloat32 get: - { index: \u0026#34;1\u0026#34;, operation: \u0026#34;get\u0026#34;, deviceResource: \u0026#34;randfloat32\u0026#34;} - name: testrandfloat64 get: - { index: \u0026#34;1\u0026#34;, operation: \u0026#34;get\u0026#34;, deviceResource: \u0026#34;randfloat64\u0026#34;} - name: testping get: - { index: \u0026#34;1\u0026#34;, operation: \u0026#34;get\u0026#34;, deviceResource: \u0026#34;ping\u0026#34;} - name: testmessage get: - { index: \u0026#34;1\u0026#34;, operation: \u0026#34;get\u0026#34;, deviceResource: \u0026#34;message\u0026#34;} set: - { index: \u0026#34;1\u0026#34;, operation: \u0026#34;set\u0026#34;, deviceResource: \u0026#34;message\u0026#34;} coreCommands: - name: testrandfloat32 get: path: \u0026#34;/api/v1/device/{deviceId}/testrandfloat32\u0026#34; responses: - code: \u0026#34;200\u0026#34; description: \u0026#34;get the random float32 value\u0026#34; expectedValues: [\u0026#34;randfloat32\u0026#34;] - code: \u0026#34;500\u0026#34; description: \u0026#34;internal server error\u0026#34; expectedValues: [] - name: testrandfloat64 get: path: \u0026#34;/api/v1/device/{deviceId}/testrandfloat64\u0026#34; responses: - code: \u0026#34;200\u0026#34; description: \u0026#34;get the random float64 value\u0026#34; expectedValues: [\u0026#34;randfloat64\u0026#34;] - code: \u0026#34;500\u0026#34; description: \u0026#34;internal server error\u0026#34; expectedValues: [] - name: testping get: path: \u0026#34;/api/v1/device/{deviceId}/testping\u0026#34; responses: - code: \u0026#34;200\u0026#34; description: \u0026#34;ping the device\u0026#34; expectedValues: [\u0026#34;ping\u0026#34;] - code: \u0026#34;500\u0026#34; description: \u0026#34;internal server error\u0026#34; expectedValues: [] - name: testmessage get: path: \u0026#34;/api/v1/device/{deviceId}/testmessage\u0026#34; responses: - code: \u0026#34;200\u0026#34; description: \u0026#34;get the message\u0026#34; expectedValues: [\u0026#34;message\u0026#34;] - code: \u0026#34;500\u0026#34; description: \u0026#34;internal server error\u0026#34; expectedValues: [] put: path: \u0026#34;/api/v1/device/{deviceId}/testmessage\u0026#34; parameterNames: [\u0026#34;message\u0026#34;] responses: - code: \u0026#34;204\u0026#34; description: \u0026#34;set the message.\u0026#34; expectedValues: [] - code: \u0026#34;500\u0026#34; description: \u0026#34;internal server error\u0026#34; expectedValues: [] デバイスプロファイルは、デバイスと一対一ではなく、例えばデバイスのモデルごとにひとつ用意するイメージです 。\nこの例では、冒頭の 6 行でデバイスのモデルを定義しています。先ほど動作させたデバイス（のシミュレータ）は、Dell 製の MQTT-2 というモデルである、という想定で書かれています。\nその後、大きく deviceResources、deviceCommands、coreCommands、の三つのセクションが続いています。\ndeviceResources セクションでは、デバイスが持っているリソースと、それが持つ値の意味を決めています。この例では、このデバイスにリソース randfloat64 があり、それは 0.00 から 100.00 の範囲の Float64 型の値を持つと定義されています。また、message リソースは String 型の値を持ち、他のリソースと違って書き換えが可能（W）と定義されていることがわかります。\ndeviceCommands セクションでは、このデバイスに対して実行できるコマンドを定義し、そのコマンドのメソッド（get または set）ごとに参照または操作されるリソースを紐付けています。例えば、testrandfloat64 コマンドの get メソッドは、先に deviecResources で定義したリソース randfloat64 からの値の読み取りを行えるように定義されています。また、testmessage コマンドでは、set または get メソッドにより、リソース message の値の取得や変更ができるように定義されています。なお、ここでは実装されていませんが、ひとつのメソッドに複数のリソースを紐付けることも可能です。つまり、一回の get 操作で複数のリソースの値を同時に取得するようにも構成できます。Edge Xpert のドキュメントでは、そのような例が解説され ています。\ncoreCommands セクションでは、コアサービス層からデバイスに対して実行できるコマンドを定義しています。例えば、コアサービスが testrandfloat64 コマンドに GET リクエストを実行すると、その命令はパス /api/v1/device/{deviceId}/testrandfloat64 に渡されます。このパスは、先の deviceCommands で定義した testrandfloat64 コマンドを表すものです。そしてコアサービスは、結果としてレスポンスコード 200 が返ってきた場合は、そのレスポンスボディに含まれる値を randfloat32 の値として取り込むように構成されています。また、testmessage コマンドのみ、ほかのコマンドと異なり PUT リクエストの動作が定義されています。\nデバイスサービス設定 今回は、configuration.toml を利用します。中身は以下の通りです。\nこのファイルは環境依存の固定値を含みます。自分の環境で試す場合は、IP アドレスを環境に合わせて書き換えてください。\n$ cat mqtt/configuration.toml # configuration.toml [Writable] LogLevel = \u0026#39;DEBUG\u0026#39; [Service] Host = \u0026#34;edgex-device-mqtt\u0026#34; Port = 49982 ConnectRetries = 3 Labels = [] OpenMsg = \u0026#34;device mqtt started\u0026#34; Timeout = 5000 EnableAsyncReadings = true AsyncBufferSize = 16 [Registry] Host = \u0026#34;edgex-core-consul\u0026#34; Port = 8500 CheckInterval = \u0026#34;10s\u0026#34; FailLimit = 3 FailWaitTime = 10 Type = \u0026#34;consul\u0026#34; [Logging] EnableRemote = false File = \u0026#34;./device-mqtt.log\u0026#34; [Clients] [Clients.Data] Name = \u0026#34;edgex-core-data\u0026#34; Protocol = \u0026#34;http\u0026#34; Host = \u0026#34;edgex-core-data\u0026#34; Port = 48080 Timeout = 50000 [Clients.Metadata] Name = \u0026#34;edgex-core-metadata\u0026#34; Protocol = \u0026#34;http\u0026#34; Host = \u0026#34;edgex-core-metadata\u0026#34; Port = 48081 Timeout = 50000 [Clients.Logging] Name = \u0026#34;edgex-support-logging\u0026#34; Protocol = \u0026#34;http\u0026#34; Host =\u0026#34;edgex-support-logging\u0026#34; Port = 48061 [Device] DataTransform = true InitCmd = \u0026#34;\u0026#34; InitCmdArgs = \u0026#34;\u0026#34; MaxCmdOps = 128 MaxCmdValueLen = 256 RemoveCmd = \u0026#34;\u0026#34; RemoveCmdArgs = \u0026#34;\u0026#34; ProfilesDir = \u0026#34;/custom-config\u0026#34; # Pre-define Devices [[DeviceList]] Name = \u0026#34;MQ_DEVICE\u0026#34; Profile = \u0026#34;Test.Device.MQTT.Profile\u0026#34; Description = \u0026#34;General MQTT device\u0026#34; Labels = [ \u0026#34;MQTT\u0026#34;] [DeviceList.Protocols] [DeviceList.Protocols.mqtt] Schema = \u0026#34;tcp\u0026#34; Host = \u0026#34;192.168.0.100\u0026#34; Port = \u0026#34;1883\u0026#34; ClientId = \u0026#34;CommandPublisher\u0026#34; User = \u0026#34;\u0026#34; Password = \u0026#34;\u0026#34; Topic = \u0026#34;CommandTopic\u0026#34; [[DeviceList.AutoEvents]] Frequency = \u0026#34;30s\u0026#34; OnChange = false Resource = \u0026#34;testrandfloat64\u0026#34; # Driver configs [Driver] IncomingSchema = \u0026#34;tcp\u0026#34; IncomingHost = \u0026#34;192.168.0.100\u0026#34; IncomingPort = \u0026#34;1883\u0026#34; IncomingUser = \u0026#34;\u0026#34; IncomingPassword = \u0026#34;\u0026#34; IncomingQos = \u0026#34;0\u0026#34; IncomingKeepAlive = \u0026#34;3600\u0026#34; IncomingClientId = \u0026#34;IncomingDataSubscriber\u0026#34; IncomingTopic = \u0026#34;DataTopic\u0026#34; ResponseSchema = \u0026#34;tcp\u0026#34; ResponseHost = \u0026#34;192.168.0.100\u0026#34; ResponsePort = \u0026#34;1883\u0026#34; ResponseUser = \u0026#34;\u0026#34; ResponsePassword = \u0026#34;\u0026#34; ResponseQos = \u0026#34;0\u0026#34; ResponseKeepAlive = \u0026#34;3600\u0026#34; ResponseClientId = \u0026#34;CommandResponseSubscriber\u0026#34; ResponseTopic = \u0026#34;ResponseTopic\u0026#34; このファイルで、実際のデバイスを定義したり、必要なパラメータを指定したりします。\n前半部分では EdgeX Foundry 内の他のサービスの情報などを指定しています。\n実際のデバイスの定義は [[DeviceList]] の部分です。デバイスプロファイル Test.Device.MQTT.Profile に紐づけてデバイス MQ_DEVICE を定義しています。また、このデバイスに対するコマンドの実行に利用する MQTT ブローカも指定しています。\n[[DeviceList.AutoEvents]] の部分では、デバイス側からは自動では配信されない値であった randfloat64 の値を定期的に取得するため、30 秒ごとに testrandfloat64 コマンドを実行するように設定しています。\n[Driver] の部分では、デバイスサービスがデバイスから情報を受け取るために購読する MQTT ブローカやトピック名を設定しています。\nなお、今回はこのファイルの中で実デバイスの定義もしてしまっていますが、デバイスはあとからも追加が可能です。また、このファイルはあくまで MQTT デバイスサービスの設定ファイルなので、利用したいデバイスサービスが違えば当然この設定ファイルで書くべき内容も変わります。\nデバイスサービスを含んだ EdgeX Foundry の起動 設定ファイルができたら、デバイスサービスを含む EdgeX Foundry を起動します。\n今回は、Docker Compose ファイルに以下を足しています。これにより、 edgexfoundry/docker-device-mqtt-go イメージがコンテナとして実行され、今回用の設定ファイルが含まれる mqtt ディレクトリがコンテナにマウントさたうえで、起動時にはその中身を利用して EdgeX Foundry にデバイスサービスとデバイスが登録されます。\ndevice-mqtt: image: edgexfoundry/docker-device-mqtt-go:1.1.1 ports: - \u0026#34;49982:49982\u0026#34; container_name: edgex-device-mqtt hostname: edgex-device-mqtt networks: - edgex-network volumes: - db-data:/data/db - log-data:/edgex/logs - consul-config:/consul/config - consul-data:/consul/data - ./mqtt:/custom-config depends_on: - data - command entrypoint: - /device-mqtt - --registry=consul://edgex-core-consul:8500 - --confdir=/custom-config では、実際にこのファイルを利用して EdgeX Foundry を起動させます。\ndocker-compose up -d 動作確認 まずは、デバイスサービスやデバイスが正常に EdgeX Foundry に認識されているか確認し、その後、値の収集やコマンド操作を確認します。\n登録状態の確認 以前のエントリで device-virtual の存在を確認した方法 で、今回はデバイスサービス edgex-device-mqtt とデバイス MQ_DEVICE が確認できれば、登録は成功しています。ラクなのは UI か CLI ですね。\nまた、これまで紹介しませんでしたが、EdgeX Foundry はマイクロサービス群の状態管理に HashiCorp の Consul を利用しており、各サービスの起動状態や登録状態は、この Consul を通じても確認できます。\nConsul の GUI には、http://192.168.0.100:8500/ でアクセスできます。\nここで、\n[Services] タブで edgex-device-mqtt が緑色である [Key/Value] タブで edgex \u0026gt; devices \u0026gt; 1.0 に edgex-device-mqtt がある [Key/Value] タブで edgex \u0026gt; devices \u0026gt; 1.0 \u0026gt; edgex-device-mqtt \u0026gt; DeviceList \u0026gt; `` \u0026gt; Name が MQ_DEVICE である あたりが確認できれば、登録はできていると考えてよいでしょう。\nデバイスから配信されている値の収集の確認 今回のデバイスは、センサ randfloat32 の値を 15 秒ごとに自動的に MQTT トピックに配信していました。デバイスサービスはこれを受け取れるように構成しましたので、EdgeX Foundry に取り込まれているはずです。\nこれも、以前のエントリで値を確認した方法 で確認できます。これもラクなのは GUI か CLI ですが、例えば、API で確認する場合は以下の通りです。\n$ curl -s http://localhost:48080/api/v1/reading/name/randfloat32/3 | jq [ { \u0026#34;id\u0026#34;: \u0026#34;23fdac76-0870-4b9d-b653-561d8a7c0423\u0026#34;, \u0026#34;created\u0026#34;: 1579707885029, \u0026#34;origin\u0026#34;: 1579707885005379300, \u0026#34;modified\u0026#34;: 1579707885029, \u0026#34;device\u0026#34;: \u0026#34;MQ_DEVICE\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;randfloat32\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;QdpmZg==\u0026#34; }, { \u0026#34;id\u0026#34;: \u0026#34;5c086b1d-6b38-48c0-9aef-9b800f9c72ab\u0026#34;, \u0026#34;created\u0026#34;: 1579707870018, \u0026#34;origin\u0026#34;: 1579707870004742000, \u0026#34;modified\u0026#34;: 1579707870018, \u0026#34;device\u0026#34;: \u0026#34;MQ_DEVICE\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;randfloat32\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;QeGZmg==\u0026#34; }, { \u0026#34;id\u0026#34;: \u0026#34;de67c865-0406-408d-b238-d33de1fe1032\u0026#34;, \u0026#34;created\u0026#34;: 1579707855022, \u0026#34;origin\u0026#34;: 1579707855011006200, \u0026#34;modified\u0026#34;: 1579707855022, \u0026#34;device\u0026#34;: \u0026#34;MQ_DEVICE\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;randfloat32\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;Qd2Zmg==\u0026#34; } ] 時刻はエポックミリ秒（origin はエポックナノ秒）なので、変換すると 15 秒おきであることが確認できます。\n$ date -d \u0026#34;@1579707885.029\u0026#34; Wed 22 Jan 2020 03:44:45 PM UTC $ date -d \u0026#34;@1579707870.018\u0026#34; Wed 22 Jan 2020 03:44:30 PM UTC $ date -d \u0026#34;@1579707855.022\u0026#34; Wed 22 Jan 2020 03:44:15 PM UTC デバイスサービスの自動実行イベントの確認 randfloat64 の値は、自動では配信されなかったため、デバイスサービスの設定（configuration.toml）に [[DeviceList.AutoEvents]] を定義して、デバイスサービス側から 30 秒ごとに自動で収集させていました。\nこれもデータの蓄積を任意の方法で確認できます。例えば CLI で確認する場合は以下の通りです。\n$ edgex-cli reading list MQ_DEVICE -l 10 | grep randfloat64 5df1da1a-91db-41cf-99fa-962e87077395 randfloat64 MQ_DEVICE 1579709342806935600 4.924730e+00 15 seconds 15 seconds 50 years 7c33bb76-c9e9-4ef8-a410-80227e236d13 randfloat64 MQ_DEVICE 1579709312695223100 3.734020e+00 45 seconds 45 seconds 50 years 053987f1-5a89-4cc2-a433-03432caa9039 randfloat64 MQ_DEVICE 1579709282611878800 9.320210e+00 About a minute About a minute 50 years これも変換すると 30 秒おきであることが確認できます。\n$ date -d \u0026#34;@1579709342.806935600\u0026#34; Wed Jan 22 16:09:02 UTC 2020 $ date -d \u0026#34;@1579709312.695223100\u0026#34; Wed Jan 22 16:08:32 UTC 2020 $ date -d \u0026#34;@1579709282.611878800\u0026#34; Wed Jan 22 16:08:02 UTC 2020 デバイスへのコマンド実行の確認 このデバイスは、リソース message の値は EdgeX Foundry から変更できるような仕様で、デバイスプロファイルでもそれに合わせて PUT リクエストの挙動を定義していました。\nでは、この操作ができることを確認します。\nデバイスプロファイルで定義した各コマンドは、内部では REST エンドポイントでリクエストを待ち受けてており、そしてこの URL は、API 経由で確認できます。具体的には、デバイスを示すエンドポイントへの GET リクエストの結果に含まれます。\n$ curl -s http://localhost:48082/api/v1/device/name/MQ_DEVICE | jq ... \u0026#34;id\u0026#34;: \u0026#34;77fccbb8-f33b-42d8-bf21-6d55cdde2068\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;MQ_DEVICE\u0026#34;, ... \u0026#34;commands\u0026#34;: [ ... \u0026#34;name\u0026#34;: \u0026#34;testmessage\u0026#34;, ... \u0026#34;get\u0026#34;: { \u0026#34;path\u0026#34;: \u0026#34;/api/v1/device/{deviceId}/testmessage\u0026#34;, ... \u0026#34;url\u0026#34;: \u0026#34;http://edgex-core-command:48082/api/v1/device/77fccbb8-f33b-42d8-bf21-6d55cdde2068/command/4daa4148-7c0e-4cfd-81cc-b2b740bb4de4\u0026#34; }, \u0026#34;put\u0026#34;: { \u0026#34;path\u0026#34;: \u0026#34;/api/v1/device/{deviceId}/testmessage\u0026#34;, ... \u0026#34;url\u0026#34;: \u0026#34;http://edgex-core-command:48082/api/v1/device/77fccbb8-f33b-42d8-bf21-6d55cdde2068/command/4daa4148-7c0e-4cfd-81cc-b2b740bb4de4\u0026#34;, ... EdgeX Foundry がデバイスにコマンドを実行するときは、内部ではこの URL に対して GET なり PUT なりをリクエストするわけです。そしてデバイスサービスがそれを受け取って、設定したとおりに翻訳してデバイスへの操作が実行され応答が返ることになります。\nここでは実際に、確認できた URL にリクエストを投げます。外部からこの URL を直接叩くときは、ホスト名部分を環境に合わせて書き換えたうえで叩きます。今回であれば、次のような操作で現在値が確認できます。\n$ curl -s http://localhost:48082/api/v1/device/77fccbb8-f33b-42d8-bf21-6d55cdde2068/command/4daa4148-7c0e-4cfd-81cc-b2b740bb4de4 | jq { \u0026#34;device\u0026#34;: \u0026#34;MQ_DEVICE\u0026#34;, \u0026#34;origin\u0026#34;: 1579710432121478400, \u0026#34;readings\u0026#34;: [ { \u0026#34;origin\u0026#34;: 1579710432109675800, \u0026#34;device\u0026#34;: \u0026#34;MQ_DEVICE\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;message\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;test-message\u0026#34; } ], \u0026#34;EncodedEvent\u0026#34;: null } まだ何も変更していないので、初期値（mqtt-script.js で定義されている）が返ってきました。\nでは、変更の PUT を投げます。JSON をリクエストボディに含めて PUT します。\ncurl -s -X PUT -d \u0026#39;{\u0026#34;message\u0026#34;:\u0026#34;modified-message\u0026#34;}\u0026#39; http://localhost:48082/api/v1/device/77fccbb8-f33b-42d8-bf21-6d55cdde2068/command/4daa4148-7c0e-4cfd-81cc-b2b740bb4de4 エラーがなければ、再度 GET すると、値が変わっていることが確認できます。\nところで、URL は /api/v1/device/\u0026lt;デバイス ID\u0026gt;/command/\u0026lt;コマンド ID\u0026gt; なパスですが、実はわざわざ ID を調べなくても、/api/v1/device/name/\u0026lt;デバイス名\u0026gt;/command/\u0026lt;コマンド名\u0026gt; でも機能します。というわけで、お好みに合わせてどちらを使ってもよいですが、せっかくなので今度はこちらに GET します。\n$ curl -s http://localhost:48082/api/v1/device/name/MQ_DEVICE/command/testmessage | jq { \u0026#34;device\u0026#34;: \u0026#34;MQ_DEVICE\u0026#34;, \u0026#34;origin\u0026#34;: 1579710963293832000, \u0026#34;readings\u0026#34;: [ { \u0026#34;origin\u0026#34;: 1579710963283338200, \u0026#34;device\u0026#34;: \u0026#34;MQ_DEVICE\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;message\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;modified-message\u0026#34; } ], \u0026#34;EncodedEvent\u0026#34;: null } 値が変わっていることが確認できました。\nなお、このような GET や PUT に相当する操作は、GUI でも実行できます。例えば Closure UI であれば、[DEVICES] 画面のデバイス MQ_DEVICE の ACTION の [Control Device] アイコンから現在値が確認できます。この裏側では、この画面を開く際に、全部のリソースに対して順番に GET が発行されています。\nこの画面では、PUT リクエストが可能なリソースには ACTION 欄に Set Value アイコンが出るのですが、実行しようとしても手元の環境では入力欄が表示されず使えませんでした……。\nClosure UI ではない標準 UI であれば、PUT も実行できました。\nまとめ 新しいデバイス（のシミュレータ）を用意し、それに合わせたデバイスサービスを構成して、このデバイスを EdgeX Foundry の制御下においたうえで、実際の動作を確認できました。\n実際のシーンでは、本当のデバイスの本当の仕様に合わせてデバイスサービスを構成する必要があり、場合によってはデバイスサービスそれ自体の実装を含めてカスタマイズが必要になる可能性ももちろんあるわけです。\nが、今回確認した通り、デバイスや環境に依存する部分（リソースやコマンドの名前や数、MQTT ブローカのホスト情報やトピック名など）は、デバイスプロファイルやデバイスサービス設定として別ファイルで定義できるようになっていて、デバイスサービスの実装それ自体は、設定ファイルに従って淡々と動くだけでもあります。\n例えば Raspberry Pi に接続したセンサの値を取り込みたい場合を考えると、実際のセンサ値を MQTT トピックに投げる部分は自製する必要がありますが、であればその際に、逆に今回使ったこのデバイスサービスの仕様を前提にした投げ方にしてしまうことも可能です。そうすれば、このデバイスサービスをそのまま実際に流用できることになります。\n今回は MQTT の例でしたが、それ以外のプロトコル用の参考実装も、環境依存の部分が設定ファイル群で外在化できるような汎用性のある形で作られているようなので、それなりに様々なデバイスで使いまわせそうです。\nなお、EdgeX Foundry に取り込まれたデータは、前回のエントリで扱ったエクスポートの設定 や、アプリケーションサービスを構成することで、簡単に持ち出せます。コアサービス層に集約される段階で、すでにデバイスの差異は抽象化されているので、もちろん今回の MQTT で収集したデータも他のデバイスの情報とまったく同じ方法でエクスポート可能です。\nEdgeX Foundry 関連エントリ 冬休みの自由研究： EdgeX Foundry (1) 概要 冬休みの自由研究： EdgeX Foundry (2) 導入とデータの確認 冬休みの自由研究： EdgeX Foundry (3) エクスポートサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (4) MQTT デバイスサービスの追加 冬休みの自由研究： EdgeX Foundry (5) ルールエンジンによる自動制御 冬休みの自由研究： EdgeX Foundry (6) アプリケーションサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (7) Kubernetes 上で Fuji リリースを動かす EdgeX Foundry ハンズオンラボガイド公開 EdgeX Foundry： Geneva から Hanoi へ ","date":"2020-01-22T17:16:46Z","image":"/archives/2729/img/DSC06315.jpg","permalink":"/archives/2729/","title":"冬休みの自由研究： EdgeX Foundry (4) MQTT デバイスサービスの追加"},{"content":"EdgeX Foundry 関連エントリ 冬休みの自由研究： EdgeX Foundry (1) 概要 冬休みの自由研究： EdgeX Foundry (2) 導入とデータの確認 冬休みの自由研究： EdgeX Foundry (3) エクスポートサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (4) MQTT デバイスサービスの追加 冬休みの自由研究： EdgeX Foundry (5) ルールエンジンによる自動制御 冬休みの自由研究： EdgeX Foundry (6) アプリケーションサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (7) Kubernetes 上で Fuji リリースを動かす EdgeX Foundry ハンズオンラボガイド公開 EdgeX Foundry： Geneva から Hanoi へ エクスポートの仕組み 前回のエントリ では、EdgeX Foundry を稼働させて、仮想デバイスのデータが蓄積される様子を観察しました。続いては、外部へのエクスポートを試します。\nEdgeX Foundry は、エッジ内で閉じて利用することももちろん可能ですが、例えば処理したデータをクラウドに投げたい、オンプレミスの別のシステムに投げたい、など、EdgeX Foundry の外部へデータを連携したいシーンも考えられます。\nこのようなときに、エクスポートサービスを構成することで、コアサービスに届けられたデータをリアルタイムに外部に送れるようになります。\nEdgeX Foundry では、エクスポート先をエクスポートサービスのクライアントとして定義しています。クライアントは、クライアント登録サービス（図中の CLIENT REGISTRATION、今回の構成では edgex-export-client コンテナ）を通じてデータベースに接続情報を保存することで新規に登録できます。\nエクスポート先へのデータの配信は、実際には配信サービス（図中 DISTRIBUTION、edgex-export-distro コンテナ）が行っています。コアサービス層からのイベントの配信を受けて、データベースに登録されたクライアントにデータを送出する役割を担っています。\nエクスポートの準備 今回は、エクスポート方法として、\nMQTT トピックへの配信によるエクスポート REST エンドポイントへの POST によるエクスポート の二つのパターンを構成します。\n前述の通り、エクスポート先は エクスポートサービスのクライアントと考えるため、実際には、\nMQTT トピックをエクスポートクライアントとして登録する REST エンドポイントをエクスポートクライアントとして登録する という操作を行います。\nさて、今回も GitHub にもろもろ置いてあります ので、こちらをクローンして使います。\ngit clone https://github.com/kurokobo/edgex-lab-handson.git cd lab02 MQTT ブローカの準備 エクスポートクライアントとして MQTT トピックを登録するため、そのトピックを提供する MQTT ブローカを作ります。\n本来の用途を考えれば、エッジからさらにフォグかクラウドに送るイメージなので、test.mosquitto.org や CloudMQTT などのクラウドっぽいサービスに投げるほうがそれっぽいですが、今回はお試しなので、ブローカはローカルに立ててしまいます。\nといっても、コンテナイメージがあるので、おもむろに起動させるだけです。\n$ docker run -d --rm --name broker -p 1883:1883 eclipse-mosquitto Unable to find image \u0026#39;eclipse-mosquitto:latest\u0026#39; locally ... 52e107224959223a3132466b5356278f637daa855aadc3a1345c71c193deb4df 起動できたら、簡単にテストします。クライアントもコンテナのイメージが（非公式ですが）あるのでお借りして、適当なトピック名を指定して購読を開始してから、\n$ docker run --init --rm -it efrecon/mqtt-client sub -h 192.168.0.100 -t \u0026#34;edgex-handson-topic\u0026#34; -d Unable to find image \u0026#39;efrecon/mqtt-client:latest\u0026#39; locally ... Client mosqsub|6-1f6d3fb35f68 sending CONNECT Client mosqsub|6-1f6d3fb35f68 received CONNACK (0) Client mosqsub|6-1f6d3fb35f68 sending SUBSCRIBE (Mid: 1, Topic: edgex-handson-topic, QoS: 0) Client mosqsub|6-1f6d3fb35f68 received SUBACK Subscribed (mid: 1): 0 別のコンテナで同じトピックに配信します。\n$ docker run --init -it --rm efrecon/mqtt-client pub -h 192.168.0.100 -t \u0026#34;edgex-handson-topic\u0026#34; -d -m \u0026#34;TEST MESSAGE\u0026#34; Client mosqpub|6-96175cb072bd sending CONNECT Client mosqpub|6-96175cb072bd received CONNACK (0) Client mosqpub|6-96175cb072bd sending PUBLISH (d0, q0, r0, m1, \u0026#39;edgex-handson-topic\u0026#39;, ... (20 bytes)) Client mosqpub|6-96175cb072bd sending DISCONNECT 購読している側で、メッセージが配信されてくることが確認できれば成功です。\n$ docker run --init --rm -it efrecon/mqtt-client sub -h 192.168.0.100 -t \u0026#34;edgex-handson-topic\u0026#34; -d ... TEST MESSAGE デバッグメッセージが出て邪魔な場合は、-d オプションを消すと静かになります。\nREST エンドポイントの準備 続いて、エクスポートクライアントとして登録する REST エンドポイントを作ります。\n登録すると、REST エンドポイント側には単なる POST リクエストで届くので、POST されたリクエストボディが確認できれば動作確認には充分です。ここでは、Python の Flask で、リクエストボディを標準出力に吐くだけの簡単な Web サーバを作ります。\nfrom flask import Flask, request from datetime import datetime app = Flask(__name__) @app.route(\u0026#34;/api/v1/echo\u0026#34;, methods=[\u0026#34;POST\u0026#34;]) def echo(): print(\u0026#34;--\\n{}\u0026#34;.format(datetime.now())) print(request.get_data().decode(\u0026#34;utf-8\u0026#34;)) return \u0026#34;OK\u0026#34;, 200 if __name__ == \u0026#34;__main__\u0026#34;: app.run(debug=True, host=\u0026#34;0.0.0.0\u0026#34;, port=5000) リポジトリには配置済みなので、クローンしてある場合はそのまま起動できます。\n$ cd lab02/rest-endpoint $ python ./main.py * Serving Flask app \u0026#34;main\u0026#34; (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: on * Restarting with stat * Debugger is active! * Debugger PIN: 159-538-312 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit) 待ち受けができたら、cURL や Postman などで適当な POST リクエストを発行します。\n$ curl -X POST -d \u0026#39;{\u0026#34;message\u0026#34;: \u0026#34;TEST MESSAGE\u0026#34;}\u0026#39; http://192.168.0.100:5000/api/v1/echo OK Web サーバの出力に POST したリクエストボディが表示されれば成功です。\n$ python ./main.py ... -- 2020-01-21 00:03:26.503181 {\u0026#34;message\u0026#34;: \u0026#34;TEST MESSAGE\u0026#34;} 192.168.0.220 - - [21/Jan/2020 00:03:26] \u0026#34;POST /api/v1/echo HTTP/1.1\u0026#34; 200 - エクスポートクライアントの登録 ここまでで、エクスポートされればその中身が確認できる状態が整ったので、続けて EdgeX Foundry 側に実際に MQTT トピックや REST エンドポイントをエクスポートクライアントとして登録していきます。\nEdgeX Foundry が起動していない場合は、前回のエントリ なども参考にして起動させます。\ncd lab02 docker-compose up -d エクスポートクライアントの追加は、API や GUI で行えます。CLI では現時点では難しそうです。\nAPI でのエクスポートクライアントの登録 API によるエクスポートクライアントの登録は、edgex-export-client のエンドポイントに JSON で POST することで行えます。cURL や Postman などで実行できます。\nMQTT トピックをエクスポートクライアントとして登録するには、以下のような JSON を組んで、\n{ \u0026#34;name\u0026#34;: \u0026#34;edgex-handson-mqtt\u0026#34;, \u0026#34;addressable\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;edgex-handson-mqttbroker\u0026#34;, \u0026#34;protocol\u0026#34;: \u0026#34;tcp\u0026#34;, \u0026#34;address\u0026#34;: \u0026#34;192.168.0.100\u0026#34;, \u0026#34;port\u0026#34;: 1883, \u0026#34;topic\u0026#34;: \u0026#34;edgex-handson-topic\u0026#34; }, \u0026#34;format\u0026#34;: \u0026#34;JSON\u0026#34;, \u0026#34;enable\u0026#34;: true, \u0026#34;destination\u0026#34;: \u0026#34;MQTT_TOPIC\u0026#34; } これを http://localhost:48071/api/v1/registration に POST します。 登録されたクライアントの ID が返ってきます。\n$ curl -X POST -d \u0026#39;{\u0026#34;name\u0026#34;:\u0026#34;edgex-handson-mqtt\u0026#34;,\u0026#34;addressable\u0026#34;:{\u0026#34;name\u0026#34;:\u0026#34;edgex-handson-mqttbroker\u0026#34;,\u0026#34;protocol\u0026#34;:\u0026#34;tcp\u0026#34;,\u0026#34;address\u0026#34;:\u0026#34;192.168.0.100\u0026#34;,\u0026#34;port\u0026#34;:1883,\u0026#34;topic\u0026#34;:\u0026#34;edgex-handson-topic\u0026#34;},\u0026#34;format\u0026#34;:\u0026#34;JSON\u0026#34;,\u0026#34;enable\u0026#34;:true,\u0026#34;destination\u0026#34;:\u0026#34;MQTT_TOPIC\u0026#34;}\u0026#39; http://localhost:48071/api/v1/registration ebdde555-001b-4798-817a-da75b92406c7 登録したらその時点からエクスポートが開始されるので、指定した MQTT トピックを購読すれば、値が届いていることが確認できます。\n$ docker run --init --rm -it efrecon/mqtt-client sub -h 192.168.0.100 -t \u0026#34;edgex-handson-topic\u0026#34; -d ... Client mosqsub|6-1f6d3fb35f68 received PUBLISH (d0, q0, r0, m0, \u0026#39;edgex-handson-topic\u0026#39;, ... (258 bytes)) {\u0026#34;id\u0026#34;:\u0026#34;ac6d9f85-7c9f-4300-b6e1-f51f8b8e0e69\u0026#34;,\u0026#34;device\u0026#34;:\u0026#34;Random-Integer-Device\u0026#34;,\u0026#34;origin\u0026#34;:1579534468929275000,\u0026#34;readings\u0026#34;:[{\u0026#34;id\u0026#34;:\u0026#34;8c9267a0-c8a1-4e9e-9a60-90d7df8966fc\u0026#34;,\u0026#34;origin\u0026#34;:1579534468917108600,\u0026#34;device\u0026#34;:\u0026#34;Random-Integer-Device\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;Int16\u0026#34;,\u0026#34;value\u0026#34;:\u0026#34;8409\u0026#34;}]} Client mosqsub|6-1f6d3fb35f68 received PUBLISH (d0, q0, r0, m0, \u0026#39;edgex-handson-topic\u0026#39;, ... (263 bytes)) {\u0026#34;id\u0026#34;:\u0026#34;0539ed5c-68c3-4a12-a936-743ced169f7d\u0026#34;,\u0026#34;device\u0026#34;:\u0026#34;Random-Integer-Device\u0026#34;,\u0026#34;origin\u0026#34;:1579534468955468400,\u0026#34;readings\u0026#34;:[{\u0026#34;id\u0026#34;:\u0026#34;4b52dc78-efd9-4234-982c-1a17f21640f3\u0026#34;,\u0026#34;origin\u0026#34;:1579534468943312700,\u0026#34;device\u0026#34;:\u0026#34;Random-Integer-Device\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;Int32\u0026#34;,\u0026#34;value\u0026#34;:\u0026#34;-54648128\u0026#34;}]} 続けて、REST エンドポイントへのエクスポートを追加します。POST する JSON は以下です。\n{ \u0026#34;name\u0026#34;: \u0026#34;edgex-handson-rest\u0026#34;, \u0026#34;addressable\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;edgex-handson-restendpoint\u0026#34;, \u0026#34;protocol\u0026#34;: \u0026#34;http\u0026#34;, \u0026#34;method\u0026#34;: \u0026#34;POST\u0026#34;, \u0026#34;address\u0026#34;: \u0026#34;192.168.0.100\u0026#34;, \u0026#34;port\u0026#34;: 5000, \u0026#34;path\u0026#34;: \u0026#34;/api/v1/echo\u0026#34; }, \u0026#34;format\u0026#34;: \u0026#34;JSON\u0026#34;, \u0026#34;enable\u0026#34;: true, \u0026#34;destination\u0026#34;: \u0026#34;REST_ENDPOINT\u0026#34; } POST する先は同様に http://localhost:48071/api/v1/registration です。\n$ curl -X POST -d \u0026#39;{\u0026#34;name\u0026#34;:\u0026#34;edgex-handson-rest\u0026#34;,\u0026#34;addressable\u0026#34;:{\u0026#34;name\u0026#34;:\u0026#34;edgex-handson-restendpoint\u0026#34;,\u0026#34;protocol\u0026#34;:\u0026#34;http\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;POST\u0026#34;,\u0026#34;address\u0026#34;:\u0026#34;192.168.0.100\u0026#34;,\u0026#34;port\u0026#34;:5000,\u0026#34;path\u0026#34;:\u0026#34;/api/v1/echo\u0026#34;},\u0026#34;format\u0026#34;:\u0026#34;JSON\u0026#34;,\u0026#34;enable\u0026#34;:true,\u0026#34;destination\u0026#34;:\u0026#34;REST_ENDPOINT\u0026#34;}\u0026#39; http://localhost:48071/api/v1/registration 6092b921-b173-4de7-8441-f89461734c17 こちらも追加した段階からエクスポートが開始されるので、REST エンドポイント側で、POST リクエストが来ていることが確認できます。\n$ python ./main.py ... -- 2020-01-21 00:47:45.055939 {\u0026#34;id\u0026#34;:\u0026#34;a0f8347d-e3d0-4bfc-ae20-98fcb9714224\u0026#34;,\u0026#34;device\u0026#34;:\u0026#34;Random-Integer-Device\u0026#34;,\u0026#34;origin\u0026#34;:1579535264923285900,\u0026#34;readings\u0026#34;:[{\u0026#34;id\u0026#34;:\u0026#34;9c5313ef-4a04-48f1-bcd4-cea06a08c0f5\u0026#34;,\u0026#34;origin\u0026#34;:1579535264898458900,\u0026#34;device\u0026#34;:\u0026#34;Random-Integer-Device\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;Int16\u0026#34;,\u0026#34;value\u0026#34;:\u0026#34;-18516\u0026#34;}]} 192.168.0.100 - - [21/Jan/2020 00:47:45] \u0026#34;POST /api/v1/echo HTTP/1.1\u0026#34; 200 - -- 2020-01-21 00:47:45.181942 {\u0026#34;id\u0026#34;:\u0026#34;7cf0d138-ee3d-47e0-8875-877a5ea1111d\u0026#34;,\u0026#34;device\u0026#34;:\u0026#34;Random-Integer-Device\u0026#34;,\u0026#34;origin\u0026#34;:1579535264973814800,\u0026#34;readings\u0026#34;:[{\u0026#34;id\u0026#34;:\u0026#34;372eb7bf-9037-43ed-aa53-6d9cd4cafc5a\u0026#34;,\u0026#34;origin\u0026#34;:1579535264962783700,\u0026#34;device\u0026#34;:\u0026#34;Random-Integer-Device\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;Int32\u0026#34;,\u0026#34;value\u0026#34;:\u0026#34;-1557838489\u0026#34;}]} 192.168.0.100 - - [21/Jan/2020 00:47:45] \u0026#34;POST /api/v1/echo HTTP/1.1\u0026#34; 200 - API でのエクスポートクライアントの確認 登録済みのエクスポート設定は、GET リクエストで確認できます。\n$ curl -s http://localhost:48071/api/v1/registration | jq [ { \u0026#34;id\u0026#34;: \u0026#34;ebdde555-001b-4798-817a-da75b92406c7\u0026#34;, \u0026#34;created\u0026#34;: 1579534374854, \u0026#34;modified\u0026#34;: 1579534374854, \u0026#34;origin\u0026#34;: 0, \u0026#34;name\u0026#34;: \u0026#34;edgex-handson-mqtt\u0026#34;, ... }, { \u0026#34;id\u0026#34;: \u0026#34;6092b921-b173-4de7-8441-f89461734c17\u0026#34;, \u0026#34;created\u0026#34;: 1579535149239, \u0026#34;modified\u0026#34;: 1579535149239, \u0026#34;origin\u0026#34;: 0, \u0026#34;name\u0026#34;: \u0026#34;edgex-handson-rest\u0026#34;, ... } ] API でのエクスポートクライアントの削除 削除は DELETE リクエストで行えます。エンドポイントはそれぞれ以下です。\n削除対象を ID で指定する場合 http://localhost:48071/api/v1/registration/id/\u0026lt;ID\u0026gt; 削除対象を名前で指定する場合 http://localhost:48071/api/v1/registration/name/\u0026lt;名前\u0026gt; cURL で実行する場合は以下が例です。\n$ curl -X DELETE http://localhost:48071/api/v1/registration/id/ebdde555-001b-4798-817a-da75b92406c7 true $ curl -X DELETE http://localhost:48071/api/v1/registration/name/edgex-handson-rest true GUI でのエクスポートクライアントの登録 ここまで API でがんばってきましたが、前回のエントリ で紹介した Closure UI では、エクスポートクライアントの追加も削除も非常に楽に行えます。\nhttp://localhost:8080/ にアクセスしてログイン後、[EXPORT] を開きます。\nここから、画面に従って値を入れていけば完了です。例えば今回の MQTT トピックをクライアントとして登録する場合は、以下のように入力します。\n項目 値 Destination MQTT Topic Name edgex-handson-mqtt Enable オン（緑） Export format JSON Protocol TCP Address 192.168.0.100 Port 1883 Topic edgex-handson-topic 上記以外 デフォルト値 REST エンドポイントの場合は次のようにします。\n項目 値 Destination REST Endpoint Name edgex-handson-rest Enable オン（緑） Export format JSON Protocol HTTP Address 192.168.0.100 Port 5000 Path /api/v1/echo 上記以外 デフォルト値 GUI でのエクスポートクライアントの削除 削除は、登録後の画面で、削除したいクライアントの右端、[ACTION] 欄にある [Delete Export] アイコンをクリックするだけです。簡単ですね。\nアプリケーションサービスによるエクスポート ……と、意気揚々と書いてきたものの、今回利用したエクスポートサービスは最近のリリースではすでに廃止されており、エクスポートの機能は アプリケーションサービス に統合されているようです。\nアプリケーションサービスは、データに対して変換やフィルタなどさまざまな処理を行えますが、その処理のひとつとして、今回行ったようなエクスポートも行えます。複数の処理をパイプライン化してつなげることで、例えば特定のデバイスの値だけフィルタしたあとにエクスポート、などが可能です。\nエクスポートサービスでは、すべてのデータがエクスポートサービスを通るため、データ量やエクスポート先が多い場合にパフォーマンス面での問題が考えられますが、アプリケーションサービスでは、各々がコアサービス層のメッセージバスに直接つないでデータを受け取れるようにすることで、こうした問題に対応しているようですね。\nSDK も提供されており、自製しなくても単純なフィルタやエクスポートは app-service-configurable を利用して簡単に行えそうです。\nアプリケーションサービスを使ったエクスポートは、別のエントリで紹介 していますので、併せてどうぞ。\nまとめ エクスポートサービスを構成して、EdgeX Foundry で収集したデータを外部に送れることが確認できました。エクスポート先の任意のシステムでは、これらの値を取り込んで処理すればよいわけですね。\nGUI の画面上やドキュメントでは、GCP や AWS、Azure の IoT サービスもエクスポート先として挙げられていますが、現段階でどこまで動くかは確認できていません。すでに証明書などを用いた認証はできそうなので、どこかで試してみます。\nEdgeX Foundry 関連エントリ 冬休みの自由研究： EdgeX Foundry (1) 概要 冬休みの自由研究： EdgeX Foundry (2) 導入とデータの確認 冬休みの自由研究： EdgeX Foundry (3) エクスポートサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (4) MQTT デバイスサービスの追加 冬休みの自由研究： EdgeX Foundry (5) ルールエンジンによる自動制御 冬休みの自由研究： EdgeX Foundry (6) アプリケーションサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (7) Kubernetes 上で Fuji リリースを動かす EdgeX Foundry ハンズオンラボガイド公開 EdgeX Foundry： Geneva から Hanoi へ ","date":"2020-01-21T13:01:00Z","image":"/archives/2697/img/DSC06315.jpg","permalink":"/archives/2697/","title":"冬休みの自由研究： EdgeX Foundry (3) エクスポートサービスによるエクスポート"},{"content":"EdgeX Foundry 関連エントリ 冬休みの自由研究： EdgeX Foundry (1) 概要 冬休みの自由研究： EdgeX Foundry (2) 導入とデータの確認 冬休みの自由研究： EdgeX Foundry (3) エクスポートサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (4) MQTT デバイスサービスの追加 冬休みの自由研究： EdgeX Foundry (5) ルールエンジンによる自動制御 冬休みの自由研究： EdgeX Foundry (6) アプリケーションサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (7) Kubernetes 上で Fuji リリースを動かす EdgeX Foundry ハンズオンラボガイド公開 EdgeX Foundry： Geneva から Hanoi へ 動かしてみる EdgeX Foundry、動かしてみましょう。\n公式の developer-scripts リポジトリ で、リリースごとに Docker Compose ファイルが提供されているので、動かすだけならとても簡単です。\n現時点の最新は Fuji リリースです。ファイルの詳細はリポジトリの README.md に書かれていますが、簡単に試すのであれば、ひとまずはセキュリティ機能を省いた *-no-secty.yml を使うとよいでしょう。\nが、今日時点の状態だと、エクスポートサービス層がコメントアウトされていたので、このエントリの内容をそのまま試したい場合は、ぼくのリポジトリに置いてあるヤツ を使ってください。\n前提 Git と Docker、Docker Compose が動く環境を用意済みであるものとします。ぼくの家では Ubuntu 上の Docker ですが、もちろん Windows 上の Docker でも大丈夫です。\nファイルの用意と起動 流れを追えるように GitHub にもろもろ置いてあります。これをクローンして、イメージをプルします。\n$ git clone https://github.com/kurokobo/edgex-lab-handson.git $ cd lab01 $ docker-compose pull Pulling volume ... done Pulling consul ... done Pulling config-seed ... done Pulling mongo ... done Pulling logging ... done Pulling system ... done Pulling notifications ... done Pulling metadata ... done Pulling data ... done Pulling command ... done Pulling scheduler ... done Pulling app-service-rules ... done Pulling rulesengine ... done Pulling export-client ... done Pulling export-distro ... done Pulling device-virtual ... done Pulling ui ... done Pulling portainer ... done おもむろに起動させます。\n$ docker-compose up -d Creating network \u0026#34;lab01_edgex-network\u0026#34; with driver \u0026#34;bridge\u0026#34; Creating network \u0026#34;lab01_default\u0026#34; with the default driver Creating edgex-files ... done Creating lab01_portainer_1 ... done Creating edgex-core-consul ... done Creating edgex-mongo ... done Creating edgex-config-seed ... done Creating edgex-support-logging ... done Creating edgex-core-metadata ... done Creating edgex-sys-mgmt-agent ... done Creating edgex-support-notifications ... done Creating edgex-core-data ... done Creating edgex-support-scheduler ... done Creating edgex-core-command ... done Creating edgex-app-service-configurable-rules ... done Creating edgex-export-client ... done Creating edgex-ui-go ... done Creating edgex-device-virtual ... done Creating edgex-export-distro ... done Creating edgex-support-rulesengine ... done なお、Windows の場合、edgex-device-virtual がポート 49990（デフォルトのダイナミックポート範囲である 49152 以上）を使っている関係で、運が悪いと以下のようなエラーが出ることがあります。\n$ docker-compose up -d ... Starting edgex-device-virtual ... error ERROR: for edgex-device-virtual Cannot start service device-virtual: driver failed programming external connectivity on endpoint edgex-device-virtual (70409888653ac0ce83c1ee9f7df04feb181b35590fc06f0d6fd4e6b511e27cfc): Error starting userland proxy: listen tcp 0.0.0.0:49990: bind: An attempt was made to access a socket in a way forbidden by its access permissions. ERROR: for device-virtual Cannot start service device-virtual: driver failed programming external connectivity on endpoint edgex-device-virtual (70409888653ac0ce83c1ee9f7df04feb181b35590fc06f0d6fd4e6b511e27cfc): Error starting userland proxy: listen tcp 0.0.0.0:49990: bind: An attempt was made to access a socket in a way forbidden by its access permissions. ERROR: Encountered errors while bringing up the project. この場合、何らかのプロセスが予約したポートと被っている（netsh int ip show excludedportrange protocol=tcp で確認できる）ことが原因なので、いちど OS を再起動するとおそらく治ります。\ndocker-compose up -d がエラーなく完了したら、docker-compose ps で edgex-config-seed 以外の Status が Up になっていることを確認します。\n$ docker-compose ps Name Command State Ports ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- edgex-app-service-configurable-rules /app-service-configurable ... Up 48095/tcp, 0.0.0.0:48100-\u0026gt;48100/tcp edgex-config-seed /edgex/cmd/config-seed/con ... Exit 0 edgex-core-command /core-command --registry - ... Up 0.0.0.0:48082-\u0026gt;48082/tcp edgex-core-consul docker-entrypoint.sh agent ... Up 8300/tcp, 8301/tcp, 8301/udp, 8302/tcp, 8302/udp, 0.0.0.0:8400-\u0026gt;8400/tcp, 0.0.0.0:8500-\u0026gt;8500/tcp, 8600/tcp, 8600/udp edgex-core-data /core-data --registry --pr ... Up 0.0.0.0:48080-\u0026gt;48080/tcp, 0.0.0.0:5563-\u0026gt;5563/tcp edgex-core-metadata /core-metadata --registry ... Up 0.0.0.0:48081-\u0026gt;48081/tcp, 48082/tcp edgex-device-virtual /device-virtual --profile= ... Up 0.0.0.0:49990-\u0026gt;49990/tcp edgex-export-client /export-client --registry ... Up 0.0.0.0:48071-\u0026gt;48071/tcp edgex-export-distro /export-distro --registry ... Up 0.0.0.0:48070-\u0026gt;48070/tcp, 0.0.0.0:5566-\u0026gt;5566/tcp edgex-files /bin/sh -c /usr/bin/tail - ... Up edgex-mongo /edgex-mongo/bin/edgex-mon ... Up 0.0.0.0:27017-\u0026gt;27017/tcp edgex-support-logging /support-logging --registr ... Up 0.0.0.0:48061-\u0026gt;48061/tcp edgex-support-notifications /support-notifications --r ... Up 0.0.0.0:48060-\u0026gt;48060/tcp edgex-support-rulesengine /bin/sh -c java -jar -Djav ... Up 0.0.0.0:48075-\u0026gt;48075/tcp edgex-support-scheduler /support-scheduler --regis ... Up 0.0.0.0:48085-\u0026gt;48085/tcp edgex-sys-mgmt-agent /sys-mgmt-agent --registry ... Up 0.0.0.0:48090-\u0026gt;48090/tcp edgex-ui-go ./edgex-ui-server Up 0.0.0.0:4000-\u0026gt;4000/tcp lab01_portainer_1 /portainer -H unix:///var/ ... Up 0.0.0.0:9000-\u0026gt;9000/tcp この段階でできているモノ この段階で、最低限必要な一通りのモノはすでに稼働しています。\nEdgeX Foundry で制御したいデバイスは、この時点では物理的には存在していませんが、テスト用の仮想デバイスが初期状態でいくつか用意されています。これが図中に赤枠で示したもので、\nデバイス ランダムな値を生成し続けるだけのデバイス ひとつのデバイスが複数のリソースを持つ ここでいうリソースは、デバイスとやり取りできる情報の種類のこと 例えば、今回のデバイスのひとつ Random-Float-Device であれば、Float32 と Float64 という二種類のリソースを持っていて、別々に制御できる これは具体的には、例えば『空調センサ』という一つのデバイスが『温度』と『湿度』の二つの情報を持っているような状態 外部から、指定したリソースの現在のランダム値を読み取れる 外部から、指定したリソースの数値の生成ロジックを設定できる デバイスサービス 前述のデバイス群（4 つ）を管理するインタフェイス デバイスごとに一定の間隔で所定のリソースの現在の値を取得し、コアサービス層に蓄積する コアサービス層からあるリソースへの GET コマンドを受け取ると、デバイスからそのリソースの現在の値を取得して返す コアサービス層からあるリソースへの POST コマンドを受け取ると、デバイスのそのリソースの数値の生成ロジックを変更する コアサービス デバイスサービスから送られた値を蓄積する （GUI や API を通じて依頼された）デバイスに対する GET や POST のコマンド実行をデバイスサービスに指示する のように構成されています。\nすなわち、最終的に行いたい、デバイスからのセンシングとその値の蓄積、デバイスに対するアクチュエーションが、物理デバイスではないものの、仮想的なデバイスとしてすでに動いていることになります。\n実世界でいえば、デバイスは何らかの装置（スイッチとかセンサとかカメラとかいろいろ）であり、外部からはそのデバイスごとにさまざまな手順で情報を取得したり制御したりする必要があります。\nデバイスサービスは、そうしたデバイスごとのプロトコルの差異を抽象化したインタフェイスとして振舞い、デバイス種別ごとに単一のコントロールポイントを提供するわけです。\n操作手段 操作は GUI、CLI、REST API の三種類で行えます。\nといっても、日常的に何らかの操作をすべきものではなく、最初の構築時点でデバイスの登録だったりエクスポートの設定だったりルールエンジンの構成だったりをしてしまったあとは、あとは粛々と運用し続けることになるでしょう。\n現段階では、GUI がリッチではありませんが、そういう利用実態を考えればそういうものかもしれません。原則、すべての操作が REST API を介して行えるようになっているので、基本的には Postman などのツールを使って API を叩くものだと思っておいたほうがよさそうです。\nGUI での操作 GUI は OSS の範囲では二種類あります。\nedgex-ui-go 標準の Web GUI 4000 番ポートで待ち受け edgex-ui-closure 多少リッチになった GUI でもところどころ動かない 8080 番ポートで待ち受け 商用の Edge Xpert の GUI はこれがベースの模様 edgex-ui-go が標準の Web GUI です。ブラウザで 4000 番ポートにアクセスして、デフォルトのユーザ admin（パスワードも admin）でログインできます。\nローカルホストで起動させている場合は、http://localhost:4000/ でアクセスできます。\nこの GUI を通じてもろもろを操作したい場合は、まずはこの GUI で操作する対象を登録する必要があります。[Gateway] の [Add] ボタンで、コンテナをホストしているマシンの IP アドレスを登録します。\n登録したゲートウェイを選択した状態で [Device Service] に遷移すると、先述の device-virtual が登録されている様子が見えるはずです。\nさらにデバイスサービス device-virtual の [Devices] アイコンを展開すると、このデバイスサービス経由で管理している、先の図で赤枠で示したデバイス群が確認できます。\nさらにデバイスごとの [Commands] を展開すると、デバイスからの情報の取得や、制御命令の実行ができるようになっています。\n別の GUI が欲しい場合、edgex-ui-closure というモジュールも用意されています。標準の Docker Compose ファイルには含まれていませんが、今回の Docker Compose ファイルには以下を追記済みなので、起動できているはずです。\nclojure: image: edgexfoundry/docker-edgex-ui-clojure:1.1.0 ports: - \u0026#34;8080:8080\u0026#34; container_name: edgex-ui-clojure hostname: edgex-ui-clojure networks: - edgex-network volumes: - db-data:/data/db - log-data:/edgex/logs - consul-config:/consul/config - consul-data:/consul/data depends_on: - data - command ブラウザで 8080 番ポートにアクセスして、パスワード admin でログインできます。\nこの GUI の [DEVICES] 画面でも、先述の device-virtual の配下のデバイスとして制御されている 4 つのデバイスが確認できます。\nまた、デバイスごとの右端のアイコン群から、現在値の確認やコマンドの実行が行えます。\nCLI での操作 CLI として、現時点ではオフィシャルなリリースではありませんが、edgex-cli が存在しています。\nGo 言語で書かれていて、自分で make しないといけないため、環境をつくる必要がありますが、一時的に動かすならコンテナの中で触れます。\n以下、コンテナの中で動かす場合の手順です。golang:latest をそのまま使うとページャ（less とか）がなくて部分的にうまく動かなくなるので、途中で less を突っ込んでいます。\n$ docker run --rm -it golang Unable to find image \u0026#39;golang:latest\u0026#39; locally latest: Pulling from library/golang ... Status: Downloaded newer image for golang:latest # apt update \u0026amp;\u0026amp; apt install -y less Get:1 http://security.debian.org/debian-security buster/updates InRelease [65.4 kB] ... Processing triggers for mime-support (3.62) ... # git clone https://github.com/kurokobo/edgex-cli Cloning into \u0026#39;edgex-cli\u0026#39;... ... Resolving deltas: 100% (402/402), done. # cd edgex-cli # make install ... go: finding github.com/BurntSushi/toml v0.3.1 make install がエラーなく完了すれば、コマンドが使える状態です。\nただし、デフォルトだとホスト名 localhost に接続しにいくので、CLI 自体をコンテナ内で動作させている場合は接続できません。\nコマンドを一度でも実行すると、接続先の情報が ~/.edgex-cli/config.yaml に保存されるので、コマンドを空打ちしてファイルを作らせたあとに、これを修正します。\n# edgex-cli _____ _ __ __ _____ _ | ____|__| | __ _ ___\\ \\/ / | ___|__ _ _ _ __ __| |_ __ _ _ | _| / _` |/ _` |/ _ \\\\ / | |_ / _ \\| | | | \u0026#39;_ \\ / _` | \u0026#39;__| | | | | |__| (_| | (_| | __// \\ | _| (_) | |_| | | | | (_| | | | |_| | |_____\\__,_|\\__, |\\___/_/\\_\\ |_| \\___/ \\__,_|_| |_|\\__,_|_| \\__, | |___/ |___/ ... Use \u0026#34;edgex [command] --help\u0026#34; for more information about a command. # ls -la ~ ... drwxr-xr-x 2 root root 4096 Jan 19 04:01 .edgex-cli ... # grep host ~/.edgex-cli/config.yaml host: localhost # sed -i \u0026#39;s/localhost/192.168.0.100/g\u0026#39; ~/.edgex-cli/config.yaml # grep host ~/.edgex-cli/config.yaml host: 192.168.0.100 これで準備が完了です。この実装はヘルプが親切なので雰囲気でだいたい触れますが、例えば GUI で確認したようなデバイスサービスやデバイス群は以下のように確認できます。\n# edgex-cli deviceservice list Service ID Service Name Created Operating State f2dae296-0313-45e5-a13d-377fadc85276 device-virtual 4 hours ENABLED # edgex-cli device list Device ID Device Name Operating State Device Service Device Profile c085d64c-0da7-4ce3-aa31-53fa2474c38d Random-Boolean-Device ENABLED device-virtual Random-Boolean-Device 187804e7-62b2-4a9d-95d5-0f723b763048 Random-Integer-Device ENABLED device-virtual Random-Integer-Device c81a829c-f5d7-4335-987c-0c483d8c7826 Random-UnsignedInteger-Device ENABLED device-virtual Random-UnsignedInteger-Device cfd1477e-0e84-4993-9e9f-ef89b8f07c92 Random-Float-Device ENABLED device-virtual Random-Float-Device API での操作 GUI も CLI も結局は REST API にリクエストを投げたレスポンスを整形して表示しているだけなので、これがいちばん生々しく触れる手段です。これでできない操作は基本的にはないはずなので、できなかったらあきらめましょう……。\nただの REST API なので、細かくは 公式のリファレンス を参照してもらえればわかると思います。\n例えば、デバイスサービスやデバイスの一覧は以下のエンドポイントに GET を投げると JSON で返ってきます。\nデバイスサービスの一覧 http://localhost:48081/api/v1/deviceservice デバイスサービスの詳細 http://localhost:48081/api/v1/deviceservice/name/\u0026lt;デバイスサービス名\u0026gt; デバイスサービス名は一覧で取得できた name の値 http://localhost:48081/api/v1/deviceservice/\u0026lt;デバイスサービス ID\u0026gt; デバイスサービス ID は一覧で取得できた id の値 デバイスの一覧 http://localhost:48081/api/v1/device デバイスの詳細 http://localhost:48081/api/v1/device/name/\u0026lt;デバイス名\u0026gt; デバイス名は一覧で取得できた name の値 http://localhost:48081/api/v1/device/\u0026lt;デバイス ID\u0026gt; デバイス ID は一覧で取得できた id の値 蓄積データの確認 先述の通り、末端の仮想デバイスで生成されたランダムな値は、デバイスサービス virtual-device を通じてコアサービス層に蓄積されています。実際に動いていることを確認するため、蓄積された値を覗きに行きます。\nGUI でのデータの確認 いちばん簡単な手段は、先の Closure UI の [READINGS] 画面です。ここでは以下のように、各デバイスから取得したデータの履歴をサクッと確認できます。また、例えば Random-Float-Device を選択すると、Float32 と Float64 という二種類のリソースの値が別々に蓄積されていることも読み取れます。\n蓄積データの構造と API での取得 もっとも詳細な情報が得られる手段は、もちろん API です。\nデバイスサービスは、一つ以上の Reading を含む Event オブジェクトを POST することで、コアサービスにデータを送ります。\nReading 単一のデバイスの単一のリソースから取得した値の情報 Event あるデバイスサービスが一回の処理で単一のデバイスから取得した一つ以上の Reading の集合 例えば『温度と湿度はセットで GET する』ような構成を組んだ場合は、ひとつの Event に二つの Reading が含まれる Event を取得するエンドポイントに、URL にデバイス名や取得するイベント数を指定して GET すると、Reading を含む情報が得られます。今回の仮想デバイスでは、ひとつの Event にはひとつの Reading が含まれます。\n$ curl -s http://localhost:48080/api/v1/event/device/Random-Float-Device/2 | jq [ { \u0026#34;id\u0026#34;: \u0026#34;4949fc7f-069b-49b1-bc52-f905c5109692\u0026#34;, \u0026#34;device\u0026#34;: \u0026#34;Random-Float-Device\u0026#34;, \u0026#34;created\u0026#34;: 1579419158139, \u0026#34;modified\u0026#34;: 1579419158139, \u0026#34;origin\u0026#34;: 1579419158137800200, \u0026#34;readings\u0026#34;: [ { \u0026#34;id\u0026#34;: \u0026#34;6dcac272-28ad-4755-972f-ad58b02aa000\u0026#34;, \u0026#34;created\u0026#34;: 1579419158138, \u0026#34;origin\u0026#34;: 1579419158124917000, \u0026#34;modified\u0026#34;: 1579419158138, \u0026#34;device\u0026#34;: \u0026#34;Random-Float-Device\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Float32\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;fvU+Nw==\u0026#34; } ] }, { \u0026#34;id\u0026#34;: \u0026#34;f4f62305-ce8c-4f19-960b-ba2977ab0b2b\u0026#34;, \u0026#34;device\u0026#34;: \u0026#34;Random-Float-Device\u0026#34;, \u0026#34;created\u0026#34;: 1579419157643, \u0026#34;modified\u0026#34;: 1579419157643, \u0026#34;origin\u0026#34;: 1579419157642273800, \u0026#34;readings\u0026#34;: [ { \u0026#34;id\u0026#34;: \u0026#34;227aa868-6dbe-4e53-af27-c773075eac85\u0026#34;, \u0026#34;created\u0026#34;: 1579419157642, \u0026#34;origin\u0026#34;: 1579419157630999000, \u0026#34;modified\u0026#34;: 1579419157642, \u0026#34;device\u0026#34;: \u0026#34;Random-Float-Device\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Float64\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;1.258607e+308\u0026#34; } ] } ] 特定のリソースの情報に絞りたい場合は、Reading のエンドポイントにリソース名を指定して GET します。\n$ curl -s http://localhost:48080/api/v1/reading/name/Float64/2 | jq [ { \u0026#34;id\u0026#34;: \u0026#34;e1e950c0-7533-4664-a606-91c97e8dbb85\u0026#34;, \u0026#34;created\u0026#34;: 1579419397777, \u0026#34;origin\u0026#34;: 1579419397752820200, \u0026#34;modified\u0026#34;: 1579419397777, \u0026#34;device\u0026#34;: \u0026#34;Random-Float-Device\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Float64\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;8.073392e+305\u0026#34; }, { \u0026#34;id\u0026#34;: \u0026#34;2b30bdf3-cb3e-4bac-bd20-87653e9259f2\u0026#34;, \u0026#34;created\u0026#34;: 1579419367748, \u0026#34;origin\u0026#34;: 1579419367735956200, \u0026#34;modified\u0026#34;: 1579419367748, \u0026#34;device\u0026#34;: \u0026#34;Random-Float-Device\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Float64\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;-3.105079e+307\u0026#34; } ] CLI でのデータの取得 先述の CLI でも、Event や Reading は取得できます。引数でデバイス名を与える必要があるので、edgex-cli device list でデバイス名を確認してから実行するとよいでしょう。また、-l で数を絞らないと死にます。\n# edgex-cli device list Device ID Device Name Operating State Device Service Device Profile c085d64c-0da7-4ce3-aa31-53fa2474c38d Random-Boolean-Device ENABLED device-virtual Random-Boolean-Device 187804e7-62b2-4a9d-95d5-0f723b763048 Random-Integer-Device ENABLED device-virtual Random-Integer-Device c81a829c-f5d7-4335-987c-0c483d8c7826 Random-UnsignedInteger-Device ENABLED device-virtual Random-UnsignedInteger-Device cfd1477e-0e84-4993-9e9f-ef89b8f07c92 Random-Float-Device ENABLED device-virtual Random-Float-Device # edgex-cli event list Random-Float-Device -l 5 Event ID Device Origin Created Modified eb8e4094-f090-4326-be23-98babe42485f Random-Float-Device 1579437080034563100 7 seconds 7 seconds a71959a3-99a8-4cba-aecc-e2c4f90f83ce Random-Float-Device 1579437079284713500 8 seconds 8 seconds 8d5ca1d7-9e34-4a59-9140-7e07f1fcc00d Random-Float-Device 1579437050012350100 37 seconds 37 seconds cd08a429-1b0d-4639-b62d-0d7e651219d8 Random-Float-Device 1579437049270313900 38 seconds 38 seconds 04ab9a44-a6bc-409c-b328-6af41b9ab3f7 Random-Float-Device 1579437019994592700 About a minute About a minute # edgex-cli reading list Random-Float-Device -l 5 Reading ID Name Device Origin Value Created Modified Pushed 9153fa3a-12ad-42fa-980f-867e3b5bd750 Float32 Random-Float-Device 1579437080017207500 /oyN8A== 23 seconds 23 seconds 50 years ce144555-6652-4514-b28e-647e9fcc3dc9 Float64 Random-Float-Device 1579437079271713200 -7.071508e+307 24 seconds 24 seconds 50 years 91f8e6e8-74b7-435c-acf1-f3a40174a7b1 Float32 Random-Float-Device 1579437049996532600 /RPlrg== 53 seconds 53 seconds 50 years be52e1ff-707a-4826-8013-6c4111db0263 Float64 Random-Float-Device 1579437049255317200 2.928376e+307 54 seconds 54 seconds 50 years 68fa0b12-c248-43ea-9dae-6497f6c343d1 Float32 Random-Float-Device 1579437019973019800 /eOKAw== About a minute About a minute 50 years エンコードされた数値のデコード API の場合も CLI の場合も、Float32 の値が /oyN8A== のように文字列で表示されていますが、これは内部的にはこの型の数値（を表すバイト列）を Base64 でエンコードして保持しているからです。\n数値を保持する際のエンコード有無は、デバイスを追加するときに作成するプロファイルの中で指定できますが、今回は事前構成済みのデバイスを利用しているため、デフォルトの設定に従ってこのようになっています。\nエンコードされた数値はデコードすると数値に戻せます。もっといいやり方がありそうな気はしますが、よくわからないので適当なデコーダを作りました。Go が動く環境（CLI での操作で使ったコンテナ内など）で利用できます。\n# git clone https://github.com/kurokobo/edgex-decode-base64.git # cd edgex-decode-base64/ # go run main.go /eOKAw== 8.163255e-37 生のデータベースの探索 ところで、API で生データが取得できると書きましたが、これらのデータは MongoDB に保管されています。MongoDB に直接アクセスすると、実際のデータを確認できます。\n外部から MongoDB Compass などでアクセスする場合は、ポート 27017 にユーザ名 core、パスワード password で認証できますが、ここではコンテナのシェルに入って確認します。\nedgex-mongo が、MongoDB の実体のコンテナです。これのシェルを取って、MongoDB のプロンプトに入ります。複数のデータベースが存在していることがわかります。\n$ docker exec -it edgex-mongo bash # mongo MongoDB shell version v4.2.0 ... \u0026gt; show databases admin 0.000GB application-service 0.000GB config 0.000GB coredata 0.011GB exportclient 0.000GB local 0.000GB logging 0.006GB metadata 0.000GB notifications 0.000GB scheduler 0.000GB Event と Reading の値は、coredata 内のコレクションに保持されています。\n\u0026gt; use coredata switched to db coredata \u0026gt; show collections event reading valueDescriptor あとは MongoDB のお作法に従って find() などを叩くのみです。遊んだら exit で抜けます。\n\u0026gt; db.reading.find().limit(5) { \u0026#34;_id\u0026#34; : ObjectId(\u0026#34;5e2471bb0e360800014be635\u0026#34;), \u0026#34;created\u0026#34; : NumberLong(\u0026#34;1579446715833\u0026#34;), \u0026#34;modified\u0026#34; : NumberLong(\u0026#34;1579446715833\u0026#34;), \u0026#34;origin\u0026#34; : NumberLong(\u0026#34;1579446715806322700\u0026#34;), \u0026#34;uuid\u0026#34; : \u0026#34;e0af3c99-be57-4cec-a59a-2af2da976d41\u0026#34;, \u0026#34;pushed\u0026#34; : NumberLong(0), \u0026#34;device\u0026#34; : \u0026#34;Random-Boolean-Device\u0026#34;, \u0026#34;name\u0026#34; : \u0026#34;Bool\u0026#34;, \u0026#34;value\u0026#34; : \u0026#34;true\u0026#34; } { \u0026#34;_id\u0026#34; : ObjectId(\u0026#34;5e2471bb0e360800014be637\u0026#34;), \u0026#34;created\u0026#34; : NumberLong(\u0026#34;1579446715857\u0026#34;), \u0026#34;modified\u0026#34; : NumberLong(\u0026#34;1579446715857\u0026#34;), \u0026#34;origin\u0026#34; : NumberLong(\u0026#34;1579446715831613700\u0026#34;), \u0026#34;uuid\u0026#34; : \u0026#34;c336b11e-8e91-4007-bd3f-1ee63d80e45b\u0026#34;, \u0026#34;pushed\u0026#34; : NumberLong(0), \u0026#34;device\u0026#34; : \u0026#34;Random-Boolean-Device\u0026#34;, \u0026#34;name\u0026#34; : \u0026#34;Bool\u0026#34;, \u0026#34;value\u0026#34; : \u0026#34;false\u0026#34; } { \u0026#34;_id\u0026#34; : ObjectId(\u0026#34;5e2471c00e360800014be639\u0026#34;), \u0026#34;created\u0026#34; : NumberLong(\u0026#34;1579446720829\u0026#34;), \u0026#34;modified\u0026#34; : NumberLong(\u0026#34;1579446720829\u0026#34;), \u0026#34;origin\u0026#34; : NumberLong(\u0026#34;1579446720815333700\u0026#34;), \u0026#34;uuid\u0026#34; : \u0026#34;2ae4befc-1fee-48ab-b621-b4d56747a446\u0026#34;, \u0026#34;pushed\u0026#34; : NumberLong(0), \u0026#34;device\u0026#34; : \u0026#34;Random-Integer-Device\u0026#34;, \u0026#34;name\u0026#34; : \u0026#34;Int16\u0026#34;, \u0026#34;value\u0026#34; : \u0026#34;9761\u0026#34; } { \u0026#34;_id\u0026#34; : ObjectId(\u0026#34;5e2471c00e360800014be63b\u0026#34;), \u0026#34;created\u0026#34; : NumberLong(\u0026#34;1579446720843\u0026#34;), \u0026#34;modified\u0026#34; : NumberLong(\u0026#34;1579446720843\u0026#34;), \u0026#34;origin\u0026#34; : NumberLong(\u0026#34;1579446720830168500\u0026#34;), \u0026#34;uuid\u0026#34; : \u0026#34;317ea1f1-0c36-4e6d-a489-b48711ff5dd6\u0026#34;, \u0026#34;pushed\u0026#34; : NumberLong(0), \u0026#34;device\u0026#34; : \u0026#34;Random-Integer-Device\u0026#34;, \u0026#34;name\u0026#34; : \u0026#34;Int64\u0026#34;, \u0026#34;value\u0026#34; : \u0026#34;-9069744988884413669\u0026#34; } { \u0026#34;_id\u0026#34; : ObjectId(\u0026#34;5e2471c00e360800014be63d\u0026#34;), \u0026#34;created\u0026#34; : NumberLong(\u0026#34;1579446720857\u0026#34;), \u0026#34;modified\u0026#34; : NumberLong(\u0026#34;1579446720857\u0026#34;), \u0026#34;origin\u0026#34; : NumberLong(\u0026#34;1579446720841898700\u0026#34;), \u0026#34;uuid\u0026#34; : \u0026#34;5be3634e-b171-4849-9bac-f13de720c11e\u0026#34;, \u0026#34;pushed\u0026#34; : NumberLong(0), \u0026#34;device\u0026#34; : \u0026#34;Random-Integer-Device\u0026#34;, \u0026#34;name\u0026#34; : \u0026#34;Int32\u0026#34;, \u0026#34;value\u0026#34; : \u0026#34;-913901160\u0026#34; } \u0026gt; exit # exit 環境の停止 ひとしきり遊んだら、クリーンアップします。方法がいくつかあります。\nまた起動させたいから、コンテナの停止だけしたいとき docker-compose stop データボリュームは残してコンテナの停止と削除だけしたいとき docker-compose down コンテナの削除だけでなく、ボリュームも Pull したイメージも何もかもを消し去りたいとき docker-compose down --rmi all --volumes 以下は完全消去の例です。\n$ docker-compose down --rmi all --volumes Stopping edgex-device-virtual ... done ... Removing image portainer/portainer まとめ EdgeX Foundry を起動させて、仮想のデバイスを利用したデータの取得や蓄積っぷりが、GUI や CLI、API などで確認できました。\n次のエントリでは、エクスポート部分を触ります。\nEdgeX Foundry 関連エントリ 冬休みの自由研究： EdgeX Foundry (1) 概要 冬休みの自由研究： EdgeX Foundry (2) 導入とデータの確認 冬休みの自由研究： EdgeX Foundry (3) エクスポートサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (4) MQTT デバイスサービスの追加 冬休みの自由研究： EdgeX Foundry (5) ルールエンジンによる自動制御 冬休みの自由研究： EdgeX Foundry (6) アプリケーションサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (7) Kubernetes 上で Fuji リリースを動かす EdgeX Foundry ハンズオンラボガイド公開 EdgeX Foundry： Geneva から Hanoi へ ","date":"2020-01-20T10:52:36Z","image":"/archives/2665/img/DSC06315.jpg","permalink":"/archives/2665/","title":"冬休みの自由研究： EdgeX Foundry (2) 導入とデータの確認"},{"content":"EdgeX Foundry 関連エントリ 冬休みの自由研究： EdgeX Foundry (1) 概要 冬休みの自由研究： EdgeX Foundry (2) 導入とデータの確認 冬休みの自由研究： EdgeX Foundry (3) エクスポートサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (4) MQTT デバイスサービスの追加 冬休みの自由研究： EdgeX Foundry (5) ルールエンジンによる自動制御 冬休みの自由研究： EdgeX Foundry (6) アプリケーションサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (7) Kubernetes 上で Fuji リリースを動かす EdgeX Foundry ハンズオンラボガイド公開 EdgeX Foundry： Geneva から Hanoi へ きっかけ 本業が IT やさんではあるもの、IoT とその周辺とは疎遠な日々を過ごしていました。\nそんなある日、あるイベントで EdgeX Foundry の存在を教えてもらい、手元に Raspberry Pi もあるものだから、いろいろ調べて実際に触ってみたわけです。IoT 関連のトレンドはまったく追えている気がしていなかったので、良い機会だしお勉強しましょうということで。\nその結果、最新の Fuji リリースがすでに家で動いているのですが、技術的な諸々は長くなったので別のエントリにします。今回は概要編的なヤツです。\nEdgeX Foundry とは 公式サイト によれば、\nThe World\u0026rsquo;s First Plug and Play Ecosystem-Enabled Open Platform for the IoT Edge.\nA highly flexible and scalable open software framework that facilitates interoperability between devices and applications at the IoT Edge, along with a consistent foundation for security and manageability regardless of use case. https://www.edgexfoundry.org/\rとされています。\nひとまずは、\nオープンソースで IoT のエッジコンピューティングを実現する フレームワークである と思って向き合えば、大外れではないでしょう。\nこれは Linux Foundation が立ち上げた LF Edge のプロジェクトのひとつで、LF Edge には 現時点で 80 社以上 がメンバとして掲載されています。\nつまり、素性のわからない謎の OSS ということではまったくなく、 実際のコントリビュータとしても Dell Technologies や Intel などの面々が精力的に活動しています。後ろ盾があるのは安心感がありますね。\nEdgeX Foundry とエッジコンピューティング クラウド、というキーワードが盛り上がり始めたころは、『とにかく全部クラウドに持っていけ！ 何でもクラウドに投げこめ！』みたいな風潮も少なからずあったと思います。\nが、その後、ハイブリッドクラウドやマルチクラウドなど諸々の議論を経て、最近は、いやいや、何をするにもやっぱり使い分けだよね、という共通認識が形成されつつあり、どちらかといえばふたたび分散型に（前向きに）回帰しています。\nIoT の文脈では、処理すべきデータが生まれる場所、あるいは操作すべきデバイスが存在する場所は、少なくともクラウドの中ではないわけで、つまり工場やら家やらオフィスやら屋外やら、目の前にある現実の身近などこかであるわけです。\nであれば、\n何万もあるセンサが毎秒クラウドに生データを送り続けるのも非効率だ クラウド上で分析にかけるにしても、大量の生データである必要はなく、分析に適した形式にフィルタ（または変換）されたデータだけあればよい クラウドに送って、クラウドで処理して、クラウドから命令が来るとなると、端的に遅延がありすぎてリアルタイムに制御できない 機微データなのでそのままはクラウドに送りたくない など、コストやら応答速度やらセキュリティやらの諸々で、\nだったらエッジ側に閉じて処理してしまったほうがよくない？ 最終的にクラウドに集めるにしても、必要なデータを必要な形にエッジ側で加工してから送ればよくない？ ということになり、ここからいわゆる『エッジコンピューティング』の概念が導出されてきます。5G も最近話題ですが、これもエッジコンピューティングの文脈でもよく出てきますね。\nとはいえ、こうしたエッジコンピューティングを実現しようとしても、いろいろなデバイスがいろいろなプロトコルを使っていることもあり、 特に産業分野での IoT の文脈では、いわゆる鉄板のアーキテクチャ的なモノが成熟しておらず、デバイスと上位の分析基盤やアプリケーションとの円滑な連携にはまだまだ課題もあるようです。\nEdgeX Foundry は、こうした IoT の世界におけるエッジコンピューティングのためのプラットフォームとしてフレームワークを提供するものであり、具体的には、デバイス群の制御やそれらからのデータの収集、加工、分析、外部への送出など、必要な一連の機能それ自体とそのデザイン手段を一元的に提供するものといえる、のではないでしょうか。\nこのフレームワークを介することで、データへのアクセスやコントロールの手段が抽象化されるため、デバイスと分析基盤やアプリケーションの相互の運用性が高まる、のだと思います。\nEdgeX Foundry のアーキテクチャ で、実装面をもう少し調べていくわけですが、公式のドキュメント がけっこう充実しているので、これを紐解くのがよさそうです。\n見ていくと、EdgeX Foundry が多数のマイクロサービスの集合体として実装されていることがわかります。 以下、アーキテクチャの画像を引用します。\nデータフローにかかわる部分だと、全体で大きく 4 つのレイヤで構成されています。レイヤごとに役割をざっくり押さえておくとよさそうです。\n触ってみた感触も踏まえて、大まかにぼくなりの書き方をすると、実デバイスに近い（サウスサイド）側から、\nデバイスサービス層 実デバイスからデータを受け取って、コアサービス層に送る コアサービス層から命令を受け取って、実デバイスを操作する コアサービス層 デバイスサービス層からのデータを蓄積する サポートサービス層（のルールエンジン）から命令を受け取って、デバイスサービス層にコマンドを発行する エクスポートサービス層にデータを送る EdgeX Foundry 内のサービス群やオブジェクトのメタデータを管理する サポートサービス層 いわゆる分析機能（今のところは簡単なルールエンジンのみ）を提供する アラートやロギングの機能を提供する エクスポート済みのデータの削除を行う エクスポートサービス層 EdgeX Foundry の外にデータを送出する というところでしょうか。\n実装は Go 言語が主のようです。動作させるためのハードウェア要件も厳しくなく、Raspberry Pi 上でも動かせるみたいですね。また、起動させるだけなら Docker Compose でさくっとできます。\n家では、vSphere 上の Ubuntu 仮想マシン上に Docker Compose で動作させています。\nEdgeX Foundry でできそうなこと さて、で、つまり何ができるの、というところですが、ざっくり技術的な目線では、\n様々なデバイスからの情報取得やそれらの操作が EdgeX Foundry に集約できる デバイスサービスが実デバイスに対するインタフェイスとして機能する デバイスごとの機種やプロトコルの差異をデバイスサービスが吸収するので、実デバイスの実装を意識しないで統一された手法（REST API や GUI）でデバイスにアクセスできる デバイスサービスはさまざまなプロトコル（REST、Modbus、MQTT、ZigBee、……）に対応しており、参考実装が GitHub にある SDK が提供され、比較的容易に自製できる デバイスサービスは製品への組み込みも視野に入っている ルールエンジンにより、イベントドリブンなデバイスの制御ロジックを EdgeX Foundry で完結する形で組み込める あるデバイスのセンサの値が閾値を越えたら別のデバイスのスイッチを入れる、など EdgeX Foundry 内の通信も REST API と ZeroMQ なので、他の分析基盤やアプリケーションとも連携させられそう 集約したデータは外部へ送り出せる MQTT での発行や REST エンドポイントへの POST などができる いわゆるストア＆フォワード、常時接続でない環境でいったん自分の中に貯めておいて接続できたらまとめて送る、ができる模様 マイクロサービス化されており、フォグコンピューティング的な階層化やサービス単位での機能追加、入れ替えにも柔軟に対応できる どこに何をおいてもよい エッジに全部おいてもいいし、クラウドに全部おいてもいい 例えば、デバイスサービスは Raspberry Pi、コアサービスは普通のサーバに置いて、分析系はつよいマシンに置くとか エコシステムが成熟するとアドオンできるサービスも増えそうだ などは挙げられそうです。\n関連しそうなプロジェクト EdgeX Foundry は OSS ですが、これを商用製品としたのが IOTech の Edge Xpert だそうです。\nこのドキュメントは追い切れていませんが、少なくともデバイスサービスとエクスポートサービスのバリエーションは、OSS 版に加えてバンドルされたテンプレートがたくさんありそうでした。便利そうです。\nまた、IOTech は組み込み系などリソースや時間の制約に厳しい環境のための軽量・高速版ともいえる Edge XRT も発表しています。\nほか、競合する製品というと、そもそも知っている範囲が狭いのですが、オープンソース系だと KubeEdge、パブリッククラウド系だと Google Cloud IoT や Azure IoT Edge、AWS IoT Greengrass あたりになるでしょうか。\nとはいえ、パブリッククラウド系の IoT サービスだと、どうしても中心はクラウド側で、最終的にはクラウドありきになってくるので、言い方が『クラウドで行う処理をエッジにオフロードする』というニュアンスになってきますね。トップダウン的な。\nEdgeX Foundry は、その文脈でいうとボトムアップ的なアプローチっぽさがあります。この見方では、クラウドは必ずしも登場はしません。\n動かしてみる 冒頭で書いたとおり、最新リリースの Fuji がいま家で動いています。\nせっかくなので、導入部分、デバイスから値を貯める部分、デバイスを動かす部分、エクスポートする部分、ルールエンジンを使う部分、あたりに分けて、今後エントリを書いてみようかという気持ちでいます。先走ってタイトルに (1) って付けてしまった……。\nいろいろリンク https://www.edgexfoundry.org/ 本家 https://docs.edgexfoundry.org/ 本家のドキュメント https://github.com/edgexfoundry 本家の GitHub https://www.iotechsys.com/cmsfiles/iotech_systems/docs/edgexpert/index.html 商用版の Edge Xpert のドキュメント 本家ドキュメントにないことも書いてあることがあり、両方読むと理解が深まる EdgeX Foundry 関連エントリ 冬休みの自由研究： EdgeX Foundry (1) 概要 冬休みの自由研究： EdgeX Foundry (2) 導入とデータの確認 冬休みの自由研究： EdgeX Foundry (3) エクスポートサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (4) MQTT デバイスサービスの追加 冬休みの自由研究： EdgeX Foundry (5) ルールエンジンによる自動制御 冬休みの自由研究： EdgeX Foundry (6) アプリケーションサービスによるエクスポート 冬休みの自由研究： EdgeX Foundry (7) Kubernetes 上で Fuji リリースを動かす EdgeX Foundry ハンズオンラボガイド公開 EdgeX Foundry： Geneva から Hanoi へ ","date":"2020-01-18T14:07:50Z","image":"/archives/2629/img/DSC06315.jpg","permalink":"/archives/2629/","title":"冬休みの自由研究： EdgeX Foundry (1) 概要"},{"content":"高校二年生のとき、中学生の頃に買ったゲームボーイアドバンスを、初めていろいろ改造した。\nホワイトだった本体をウレタン塗料でがっつり黒に塗り、電源の LED を緑から青に変え、そして Afterburner と呼ばれるフロントライトを追加して、さらにその光量調節をボタン操作で行えるようにするチップ（Stealth Dimmer Chip）を取り付け。\n今では考えられないけれど、ゲームボーイアドバンス（というか当時の携帯ゲーム機全般）はもともと光源を何も積んでおらず、致命的に画面が暗かった。\nだからこのフロントライトも、当時はけっこうインパクトがあり、わくわくしながら取り付けたものである。実際、暗所でもこれだけの視認性が得られる。\nで、それから 15 年ちかく経った最近、ゲームボーイアドバンス用の交換用のバックライト付き液晶の存在を知った。もちろん純正品ではない。\nとくに遊びたいゲームがあったわけではないのだけれど、久しぶりに何かを改造したい欲がもこもこしてきたので、とりあえずポチり、台風で家に引きこもっている最中にちまちまと交換したという具合。\nまずは前述の Stealth Dimmer Chip を取り外さないといけない。この半田付け、高校生のぼくにはものすごく難易度が高くて苦労した記憶がある。今見ると配線はキレイだけれど、半田付けがものすごく汚い……。イモもいいところである。とはいえ、15 年動いてくれたわけだけれど。\nこれがもともと付けていた Afterburner。雑に言えば、液晶の手前に配置する、光る透明な板。\nこんな感じで光る。\n懐かしさを覚えつつ、全部とっぱらって、左のモノに交換。\n試しに通電させた時点で、技術の進歩ってすごいなあと思った。明るいし…… ドットが細かいし……。\nこの液晶、解像度が元の 4 倍で、従来の 1 ドットを 2 × 2 の 4 ドットで描画している。ドットピッチが倍なので、単に明るいだけでなく、明らかに見た目がきれい。\nただし、元の液晶よりも一回り大きいので、本体のケースをごりごり物理的に削らないと所定の位置に収まらない。\n単に部品を交換するだけだとあっさりすぎるので、改造している感があってちょうどよかった（？）。\nで、こうなりました。\nとても明るい。そしてきれい。暗所でこれだけ見えて恐ろしい。コントラストがエグい。\n並べると圧倒的な差。満足です。\n","date":"2019-10-13T10:35:24Z","image":"/archives/2546/img/DSC05535.jpg","permalink":"/archives/2546/","title":"ゲームボーイアドバンスの液晶を交換した話"},{"content":"実際の画面 こんな感じです。健気でかわいいですね。\nこの会話で実際に我が家のエアコンがついたり消えたりしています。\nもくろみ 人間用のインタフェイスを LINE にしたのは、いつも出先から帰り始めるときに帰り始める宣言を LINE でしていたからで、そしてそれをトリガにできれば日常にすんなり入り込ませられることが期待できたからです。\nもともと Nature Remo には、『帰り際に出先からエアコンをつけて、家に着くころには家は涼しくなっている』ことの実現を期待していたのですが、プロダクト自体には充分すぎるポテンシャルがあるものの、実際は『出先からエアコンをつける』という操作が存在することそれ自体にぼくの脳が馴染めず、結局、最近のめたくそに暑い家に帰る日々はまったく改善されなかったのでした……。\nで、そうすると、つまり『エアコンの操作のために人間に特別な運用が要求される状態』そのものを回避する必要が出てくるわけです。よって、\n日常的に使う文化が醸成できていないモノをインタフェイスにしても結局忘れるだろう。つまり、Slack や Nature Remo のネイティブアプリケーション、専用の Web インタフェイス、メールなどは、それをトリガとして運用を定めても使わなくなりそうだ 位置情報をトリガにした設定は、エリアの設定のよい塩梅を探るのが難しく（遅すぎ・早すぎ、動きすぎ・動かなすぎ）、また、Nature Remo のアプリケーションのバックグラウンド動作が殺されるとそもそも操作がトリガされない（ように見えた）ので、信頼性はそこまで高くない な感じで、エアコンをつけてほしいタイミングで日常的にしている操作、つまり帰り始める宣言をトリガにするくらいが現実的な落としどころでは、という判断です。\nLINE のボットは、ボット側で設定すればグループトークにも参加させられるので、今回の実装であれば、家族のグループに足してしまえばあとは何もしなくても、勝手にボット側から質問してきてくれます。\nつくり LINE@（と Messaging API）と AWS の Lambda（と API Gateway）、Nature Remo の API でできています。Lambda のランタイムは Node.js です。\n前述のとおり、インタフェイスが LINE でさえあればバックエンド側の実装はもはや何でもよかったので、このあたりは完全に興味だけで選んでいます。なるべく自分が触ったことがない技術で、なるべくレガシィは避けてイマドキ感のあるアーキテクチャで、ググってあまり事例がたくさん出てこないヤツ……くらいの基準です。\n最近のこの手のオートメーションって、同じことを実現するだけでもその手段は本当に山のようにあって、今回も例えば IFTTT の API を使ってもよかったし、Integromat もアリだったし、Heroku でもよかったし、Lambda にしても Python でもよかったし、Node.js でも Promise 使わずコールバック地獄に溺れてもよかったし、とか。ただ、おしごとではなく趣味でやっているわけで、難易度や実装速度、実績や堅牢・安定性よりも、なによりも興味関心の充足に全フリできるように要素を組み合わせたかったのでした。\n普段のおしごとはべつに Web やさんでも AWS やさんでもないので、まだ全然ぼくの知らない解はたくさんあるのだろうと思うと、日常的に情報を吸い上げて選択肢を増やしておかないとこの世界は生きていけない感が強いですね。存在すら知らないものは、そもそも使う使わないの選択肢に乗せられないですし。\nしかしいざ作り始めると、とにかく動くだけの状態に持っていくのは思っていたよりも本当に相当簡単で、サーバレスアーキテクチャも流行るわけだなあという気持ちです。便利な世の中ですね。触り始めたらおもしろくなってきたので、エアコンの操作を充実させるだけでなく、連携させる家電も増やしていきたいです。\n朝起きたらコーヒーが入っている状態にできないかな……。\n","date":"2018-07-29T03:54:02Z","image":"/archives/2509/img/Screenshot_20180729-015240.png","permalink":"/archives/2509/","title":"家のエアコンを操作してくれる LINE ボットを作った"},{"content":"おさらい 前回までのエントリでは、Photon OS をデプロイして初期設定を行いました。\nIntel NUC に ESXi 6.5 を入れる Photon OS をデプロイして Docker を動かす もともとの目的は、vCenter Server Appliance 6.5（vCSA 6.5）をお行儀のよい構成でインストールするために必要な DNS サーバを作ることです。よって今回は、前回構築した Photon OS 上で、bind をサービスする Docker コンテナを作ります。\n設計 こんな感じにします。\nベース OS は Alpine Linux にする bind を追加する bind の設定ファイル群はホスト OS 上のディレクトリに置き、コンテナからマウントさせる Docker Compose で起動・停止できるようにする ベース OS の選択には特にこだわりはないので、流行に乗って Alpine Linux にします。最新版を使います。\n設定ファイルの置き場所は悩みどころですが、設定の変更などがしやすくなるのでこうしました。ポータビリティは下がりますが、配布予定もないし単一のサーバでしかホストしないのでよしとします。\nDocker Compose は蛇足です。本来は複数のコンテナを連携させるときに使うものですが、使ってみたかったので使ってみます。\n以降、やっぱり文字ばかりになるので、きれいな孔雀の写真を載せておきます。配色が Photon OS のそれと似ていますね。\nDockerfile を作る まずは Dockerfile です。作ります。コンテナイメージのレシピみたいなものです。\nkuro@kuro-ph01 [ ~ ]$ vim Dockerfile kuro@kuro-ph01 [ ~ ]$ cat Dockerfile FROM alpine:latest RUN apk add bind --no-cache EXPOSE 53/udp CMD [\u0026#34;/usr/sbin/named\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;/etc/bind/named.conf\u0026#34;, \u0026#34;-u\u0026#34;, \u0026#34;named\u0026#34;, \u0026#34;-g\u0026#34;] bind の設定ファイル群を作る コンテナの中で動かす bind のための設定ファイルを作ります。\nメインの named.conf と、ゾーン定義の二つのファイルを作りました。作ったファイルはひとつのディレクトリにまとめておきます。コンテナの中の named ユーザが読み取れるように、パーミッションも変更します。\nkuro@kuro-ph01 [ ~ ]$ mkdir conf kuro@kuro-ph01 [ ~ ]$ vim conf/named.conf kuro@kuro-ph01 [ ~ ]$ cat conf/named.conf acl localnet { 127.0.0.1; 192.168.0.0/24; }; options { version \u0026#34;unknown\u0026#34;; directory \u0026#34;/var/bind\u0026#34;; pid-file \u0026#34;/var/run/named/named.pid\u0026#34;; recursion yes; notify no; listen-on { any; }; listen-on-v6 { none; }; allow-query { localnet; }; allow-query-cache { localnet; }; allow-recursion { localnet; }; allow-transfer { none; }; forwarders { 192.168.0.1; }; }; zone \u0026#34;kuro.local\u0026#34; IN { type master; file \u0026#34;/etc/bind/kuro.local.zone\u0026#34;; }; kuro@kuro-ph01 [ ~ ]$ vim conf/kuro.local.zone kuro@kuro-ph01 [ ~ ]$ cat conf/kuro.local.zone $TTL 1h @ IN SOA ns.kuro.local postmaster.kuro.local. ( 2017030501 ; serial 1h ; refresh 15m ; retry 1d ; expire 1h ; minimum ); @ IN NS ns.kuro.local. ns IN A 192.168.0.249 kuro-vc01 IN A 192.168.0.250 kuro-esx01 IN A 192.168.0.251 kuro@kuro-ph01 [ ~ ]$ chmod 755 conf kuro@kuro-ph01 [ ~ ]$ chmod 644 conf/* コンテナイメージをビルドして起動する ここまで用意ができたら、あとはビルドして起動させるだけです。\nまずはビルドします。ビルドは build サブコマンドです。末尾でビルドしたい Dockerfile を含むディレクトリを指定します。今回はカレントディレクトリなのでドット（.）です。\nkuro@kuro-ph01 [ ~ ]$ docker build -t bind . Sending build context to Docker daemon 11.78 kB Step 1 : FROM alpine:latest latest: Pulling from library/alpine 627beaf3eaaf: Pull complete Digest: sha256:58e1a1bb75db1b5a24a462dd5e2915277ea06438c3f105138f97eb53149673c4 Status: Downloaded newer image for alpine:latest ---\u0026gt; 4a415e366388 Step 2 : RUN apk add bind --no-cache ---\u0026gt; Running in 84d3fb22724e fetch http://dl-cdn.alpinelinux.org/alpine/v3.5/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.5/community/x86_64/APKINDEX.tar.gz (1/5) Installing libgcc (6.2.1-r1) (2/5) Installing libxml2 (2.9.4-r1) (3/5) Installing bind-libs (9.10.4_p6-r0) (4/5) Installing libcap (2.25-r1) (5/5) Installing bind (9.10.4_p6-r0) Executing bind-9.10.4_p6-r0.pre-install Executing busybox-1.25.1-r0.trigger OK: 9 MiB in 16 packages ---\u0026gt; 29500538ea16 Removing intermediate container 84d3fb22724e Step 3 : EXPOSE 53/udp ---\u0026gt; Running in a8ea36f4c12f ---\u0026gt; f08f46db2947 Removing intermediate container a8ea36f4c12f Step 4 : CMD /usr/sbin/named -c /etc/bind/named.conf -u named -g ---\u0026gt; Running in a2fd9c1acd90 ---\u0026gt; bf1581067386 Removing intermediate container a2fd9c1acd90 Successfully built bf1581067386 kuro@kuro-ph01 [ ~ ]$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE bind latest bf1581067386 41 seconds ago 8.772 MB alpine latest 4a415e366388 2 weeks ago 3.984 MB できたイメージは images サブコマンドで確認できます。無事にできたようなので動かしてみましょう。\n起動は run サブコマンドです。バックグラウンドで起動（-d）させ、ホストのポートとディレクトリをコンテナに割り当て（-p、-v）ます。\nkuro@kuro-ph01 [ ~ ]$ docker run -d -p 53:53/udp -v $(pwd)/conf:/etc/bind --restart always --name bind bind 442a5494f13328641cc3ba6c526a65591874f01476255a47f962dbc8922e0f3b kuro@kuro-ph01 [ ~ ]$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 442a5494f133 bind \u0026#34;/usr/sbin/named -c /\u0026#34; 3 seconds ago Up 2 seconds 0.0.0.0:53-\u0026gt;53/udp bind 起動状態は ps サブコマンドで確認できます。この結果の STATUS が Up になっていれば、少なくともコンテナは正しく動いています。あとはコンテナ内で bind が正しく動いていれば問題ありません。\nというわけで、まずは自分自身に DNS のクエリを投げてみます。ミニマル構成の Photon OS には nslookup も dig もないので、bindutils をインストールしてから試します。\nkuro@kuro-ph01 [ ~ ]$ sudo tdnf install bindutils [sudo] password for kuro Sorry, try again. [sudo] password for kuro Installing: bindutils x86_64 9.10.4-1.ph1 6.86 M Total installed size: 6.86 M Is this ok [y/N]:y Downloading: bindutils 3116681 100% Testing transaction Running transaction Complete! kuro@kuro-ph01 [ ~ ]$ dig @192.168.0.249 kuro-vc01.kuro.local ; \u0026lt;\u0026lt;\u0026gt;\u0026gt; DiG 9.10.4-P1 \u0026lt;\u0026lt;\u0026gt;\u0026gt; @192.168.0.249 kuro-vc01.kuro.local ; (1 server found) ;; global options: +cmd ;; Got answer: ;; -\u0026gt;\u0026gt;HEADER\u0026lt;\u0026lt;- opcode: QUERY, status: NOERROR, id: 49190 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 2 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;kuro-vc01.kuro.local. IN A ;; ANSWER SECTION: kuro-vc01.kuro.local. 3600 IN A 192.168.0.250 ;; AUTHORITY SECTION: kuro.local. 3600 IN NS ns.kuro.local. ;; ADDITIONAL SECTION: ns.kuro.local. 3600 IN A 192.168.0.249 ;; Query time: 0 msec ;; SERVER: 192.168.0.249#53(192.168.0.249) ;; WHEN: Tue Mar 21 00:04:46 JST 2017 ;; MSG SIZE rcvd: 98 kuro@kuro-ph01 [ ~ ]$ nslookup google.com 192.168.0.249 Server: 192.168.0.249 Address: 192.168.0.249#53 Non-authoritative answer: Name: google.com Address: 172.217.25.238 定義したゾーンの情報も引けていますし、再帰問い合わせもできています。よさそうです。\n別の端末からも叩いてみます。例えば Windows から。\nC:\\Users\\kuro\u0026gt;nslookup kuro-vc01.kuro.local 192.168.0.249 サーバー: UnKnown Address: 192.168.0.249 名前: kuro-vc01.kuro.local Address: 192.168.0.250 C:\\Users\\kuro\u0026gt;nslookup google.com 192.168.0.249 サーバー: UnKnown Address: 192.168.0.249 権限のない回答: 名前: google.com Address: 216.58.197.142 よいですね。\nうまく動かない場合は、以下のように logs サブコマンドで named のログを見るか、\nkuro@kuro-ph01 [ ~ ]$ docker logs bind 20-Mar-2017 15:00:53.955 starting BIND 9.10.4-P6 \u0026lt;id:a6837d0\u0026gt; -c /etc/bind/named.conf -u named -g 20-Mar-2017 15:00:53.955 running on Linux x86_64 4.4.41-1.ph1-esx #1-photon SMP Tue Jan 10 23:46:44 UTC 2017 20-Mar-2017 15:00:53.955 built with \u0026#39;--build=x86_64-alpine-linux-musl\u0026#39; \u0026#39;--host=x86_64-alpine-linux-musl\u0026#39; \u0026#39;--prefix=/usr\u0026#39; \u0026#39;--sysconfdir=/etc/bind\u0026#39; \u0026#39;--localstatedir=/var\u0026#39; \u0026#39;--with-openssl=/usr\u0026#39; \u0026#39;--enable-linux-caps\u0026#39; \u0026#39;--with-libxml2\u0026#39; \u0026#39;--enable-threads\u0026#39; \u0026#39;--enable-filter-aaaa\u0026#39; \u0026#39;--enable-ipv6\u0026#39; \u0026#39;--enable-shared\u0026#39; \u0026#39;--enable-static\u0026#39; \u0026#39;--with-libtool\u0026#39; \u0026#39;--with-randomdev=/dev/random\u0026#39; \u0026#39;--mandir=/usr/share/man\u0026#39; \u0026#39;--infodir=/usr/share/info\u0026#39; \u0026#39;build_alias=x86_64-alpine-linux-musl\u0026#39; \u0026#39;host_alias=x86_64-alpine-linux-musl\u0026#39; \u0026#39;CC=gcc\u0026#39; \u0026#39;CFLAGS=-Os -fomit-frame-pointer -D_GNU_SOURCE\u0026#39; \u0026#39;LDFLAGS=-Wl,--as-needed\u0026#39; \u0026#39;CPPFLAGS=-Os -fomit-frame-pointer\u0026#39; 20-Mar-2017 15:00:53.955 ---------------------------------------------------- 20-Mar-2017 15:00:53.955 BIND 9 is maintained by Internet Systems Consortium, 20-Mar-2017 15:00:53.955 Inc. (ISC), a non-profit 501(c)(3) public-benefit 20-Mar-2017 15:00:53.955 corporation. Support and training for BIND 9 are 20-Mar-2017 15:00:53.955 available at https://www.isc.org/support 20-Mar-2017 15:00:53.955 ---------------------------------------------------- 20-Mar-2017 15:00:53.955 found 1 CPU, using 1 worker thread 20-Mar-2017 15:00:53.955 using 1 UDP listener per interface . . . あるいは、以下のようにしてコンテナ内のシェルに入って調査します。ps や ls をしているのはただの例なので、実際は自由にトラブルシュートしてください。\nkuro@kuro-ph01 [ ~ ]$ docker exec -it bind /bin/ash / # ps -ef PID USER TIME COMMAND 1 named 0:00 /usr/sbin/named -c /etc/bind/named.conf -u named -g 8 root 0:00 /bin/ash 12 root 0:00 ps -ef / # ls -l /etc/bind/ total 8 -rw-r--r-- 1 1000 1000 306 Mar 20 13:57 kuro.local.zone -rw-r--r-- 1 1000 1000 495 Mar 20 14:04 named.conf なお、Dockerfile で記述した通り、今回は CMD 行で named を -g で起動させています。このため、exec サブコマンドでなく attach サブコマンドを利用しても、named につながるだけで何のトラブルシュートもできない点には注意が必要です。上記のように、exec サブコマンドを利用して明示的に新規プロセスとしてシェルを起動する必要があります。また、Alpine Linux のシェルは /bin/bash ではなく /bin/ash です。\nexec サブコマンドは起動中のコンテナに対してしかできない操作なので、もしそもそもコンテナの起動がコケている場合は、上記のコマンドではシェルに入れません。この場合は、exec サブコマンドを run サブコマンドに置き換えて、明示的に起動させます。これによりコンテナの CMD 指定が /bin/ash でオーバライドされるので、named は起動されずに（＝コンテナが落ちずに）シェルがフォアグラウンドで実行されます。\nよくあるのは、docker ps したときの STATUS が Restarting のままになるトラブルです。大体の場合、named が設定ファイルの読み込みでコケてこうなります。コケて named が落ちてコンテナが終了するものの、restart オプションによって延々と再度の起動が試行されている状態です。この場合は、ファイルのパーミッションや、ボリュームマウントの指定が正しいか確認します。\nDocker Compose で操作する ここまでで当初の最低限の要件は満たせたので、あとはオマケです。\nまっさらな状態にするために、動かしていたコンテナを停止し削除、イメージも全部消しておきます。\nkuro@kuro-ph01 [ ~ ]$ docker stop bind bind kuro@kuro-ph01 [ ~ ]$ docker rm bind bind kuro@kuro-ph01 [ ~ ]$ docker rmi bind Untagged: bind:latest Deleted: sha256:bf1581067386a7eb1de3b86773c2d89a4f901ab190d6e74679c541a91e5c1d12 Deleted: sha256:f08f46db294799dbdf60b982abc0431ffb11bb046558888b434ba3cbbe47091e Deleted: sha256:29500538ea168fc7cee66a294b3ed90a3420cc299ac9d84896b2784ecf81f609 Deleted: sha256:c193e93d56d7b0dc758de256cf867bc724aa3ef55ddb14dfdb804b09303fb84e kuro@kuro-ph01 [ ~ ]$ docker rmi alpine Untagged: alpine:latest Untagged: alpine@sha256:58e1a1bb75db1b5a24a462dd5e2915277ea06438c3f105138f97eb53149673c4 Deleted: sha256:4a415e3663882fbc554ee830889c68a33b3585503892cc718a4698e91ef2a526 Deleted: sha256:23b9c7b43573dd164619ad59e9d51eda4095926729f59d5f22803bcbe9ab24c2 kuro@kuro-ph01 [ ~ ]$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES kuro@kuro-ph01 [ ~ ]$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE あとは、以下な感じになるようにディレクトリを切って、これまでに作ったファイルを移動させておきます。\n[~/docker] └ [bind] ├ Dockerfile └ [conf] ├ kuro.local.zone └ named.conf kuro@kuro-ph01 [ ~ ]$ mkdir -p docker/bind kuro@kuro-ph01 [ ~ ]$ mv Dockerfile conf docker/bind では、今回作ったコンテナを、Docker Compose で操作できるようにしてみます。\n本来は複数コンテナを簡単に連携させるために使うものですが、旨味は少ないながらも単一のコンテナを操作したいだけの目的でも使えます。\nDocker Compose Docker Compose Full Documentation まずは Docker Compose をインストールします。上記ドキュメントに記載があるコマンドをそのまま実行するだけです。\nkuro@kuro-ph01 [ ~ ]$ sudo curl -L \u0026#34;https://github.com/docker/compose/releases/download/1.11.2/docker-compose-$(uname -s)-$(uname -m)\u0026#34; -o /usr/local/bin/docker-compose % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 600 0 600 0 0 330 0 --:--:-- 0:00:01 --:--:-- 330 100 8066k 100 8066k 0 0 23737 0 0:05:47 0:05:47 --:--:-- 15090 kuro@kuro-ph01 [ ~ ]$ sudo chmod +x /usr/local/bin/docker-compose kuro@kuro-ph01 [ ~ ]$ sudo docker-compose --version docker-compose version 1.11.2, build dfed245 インストールができたら、設定ファイルを作ります。\nkuro@kuro-ph01 [ ~ ]$ vim docker/docker-compose.yml kuro@kuro-ph01 [ ~ ]$ cat docker/docker-compose.yml version: \u0026#34;2.0\u0026#34; services: bind: container_name: bind build: bind image: bind volumes: - ./bind/conf:/etc/bind ports: - 53:53/udp restart: always あとは起動させるだけです。バックグラウンドで実行させたいので、up サブコマンドに -d を付けます。\nkuro@kuro-ph01 [ ~ ]$ cd docker kuro@kuro-ph01 [ ~/docker ]$ sudo docker-compose up -d Building bind Step 1 : FROM alpine:latest latest: Pulling from library/alpine 627beaf3eaaf: Pull complete Digest: sha256:58e1a1bb75db1b5a24a462dd5e2915277ea06438c3f105138f97eb53149673c4 Status: Downloaded newer image for alpine:latest ---\u0026gt; 4a415e366388 Step 2 : RUN apk add bind --no-cache ---\u0026gt; Running in 7cb9b9b1d9c5 fetch http://dl-cdn.alpinelinux.org/alpine/v3.5/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.5/community/x86_64/APKINDEX.tar.gz (1/5) Installing libgcc (6.2.1-r1) (2/5) Installing libxml2 (2.9.4-r1) (3/5) Installing bind-libs (9.10.4_p6-r0) (4/5) Installing libcap (2.25-r1) (5/5) Installing bind (9.10.4_p6-r0) Executing bind-9.10.4_p6-r0.pre-install Executing busybox-1.25.1-r0.trigger OK: 9 MiB in 16 packages ---\u0026gt; 3f4617f7e3e7 Removing intermediate container 7cb9b9b1d9c5 Step 3 : EXPOSE 53/udp ---\u0026gt; Running in 828a66a3c5f8 ---\u0026gt; 47c21a9ae08f Removing intermediate container 828a66a3c5f8 Step 4 : CMD /usr/sbin/named -c /etc/bind/named.conf -u named -g ---\u0026gt; Running in 13e41b8d96c7 ---\u0026gt; 85edc496264d Removing intermediate container 13e41b8d96c7 Successfully built 85edc496264d WARNING: Image for service bind was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`. Creating bind docker-compose.yml の記述に従って、各サービス（コンテナ）が起動されます。build が指定されていた場合は、Dockerfile に従ってコンテナイメージのビルドも行います。\n起動状態は、ps サブコマンドで確認できます。\nkuro@kuro-ph01 [ ~/docker ]$ sudo docker-compose ps Name Command State Ports ------------------------------------------------------------------ bind /usr/sbin/named -c /etc/bi ... Up 0.0.0.0:53-\u0026gt;53/udp ログの確認は logs サブコマンドです。\nkuro@kuro-ph01 [ ~/docker ]$ sudo docker-compose logs Attaching to bind bind | 20-Mar-2017 16:20:24.015 starting BIND 9.10.4-P6 \u0026lt;id:a6837d0\u0026gt; -c /etc/bind/named.conf -u named -g bind | 20-Mar-2017 16:20:24.015 running on Linux x86_64 4.4.41-1.ph1-esx #1-photon SMP Tue Jan 10 23:46:44 UTC 2017 bind | 20-Mar-2017 16:20:24.015 built with \u0026#39;--build=x86_64-alpine-linux-musl\u0026#39; \u0026#39;--host=x86_64-alpine-linux-musl\u0026#39; \u0026#39;--prefix=/usr\u0026#39; \u0026#39;--sysconfdir=/etc/bind\u0026#39; \u0026#39;--localstatedir=/var\u0026#39; \u0026#39;--with-openssl=/usr\u0026#39; \u0026#39;--enable-linux-caps\u0026#39; \u0026#39;--with-libxml2\u0026#39; \u0026#39;--enable-threads\u0026#39; \u0026#39;--enable-filter-aaaa\u0026#39; \u0026#39;--enable-ipv6\u0026#39; \u0026#39;--enable-shared\u0026#39; \u0026#39;--enable-static\u0026#39; \u0026#39;--with-libtool\u0026#39; \u0026#39;--with-randomdev=/dev/random\u0026#39; \u0026#39;--mandir=/usr/share/man\u0026#39; \u0026#39;--infodir=/usr/share/info\u0026#39; \u0026#39;build_alias=x86_64-alpine-linux-musl\u0026#39; \u0026#39;host_alias=x86_64-alpine-linux-musl\u0026#39; \u0026#39;CC=gcc\u0026#39; \u0026#39;CFLAGS=-Os -fomit-frame-pointer -D_GNU_SOURCE\u0026#39; \u0026#39;LDFLAGS=-Wl,--as-needed\u0026#39; \u0026#39;CPPFLAGS=-Os -fomit-frame-pointer\u0026#39; bind | 20-Mar-2017 16:20:24.015 ---------------------------------------------------- bind | 20-Mar-2017 16:20:24.015 BIND 9 is maintained by Internet Systems Consortium, bind | 20-Mar-2017 16:20:24.015 Inc. (ISC), a non-profit 501(c)(3) public-benefit bind | 20-Mar-2017 16:20:24.015 corporation. Support and training for BIND 9 are bind | 20-Mar-2017 16:20:24.015 available at https://www.isc.org/support bind | 20-Mar-2017 16:20:24.015 ---------------------------------------------------- bind | 20-Mar-2017 16:20:24.015 found 1 CPU, using 1 worker thread bind | 20-Mar-2017 16:20:24.015 using 1 UDP listener per interface . . . コンテナの停止や削除は、それぞれ stop サブコマンドや rm サブコマンドで実行できます。\nkuro@kuro-ph01 [ ~/docker ]$ sudo docker-compose stop Stopping bind ... done kuro@kuro-ph01 [ ~/docker ]$ sudo docker-compose rm Going to remove bind Are you sure? [yN] y Removing bind ... done kuro@kuro-ph01 [ ~/docker ]$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 当然ながら、それぞれネイティブの docker コマンドによる操作も可能です。\nイメージの削除は docker-compose コマンドではできないので、docker コマンドで個別に行う必要があります。ただし、イメージをビルドしなおしたいだけであれば、build サブコマンドを利用するか、up サブコマンドの実行時に -build オプションを付与することで実現できます。\nまとめ Docker 上で bind を動かし、Docker Compose で操作できるようにしました。\n自前でがんばりたくない場合は、探せば Docker Hub に既成の bind サービス用コンテナがいくつかある（例えばこれとか）ので、そういうのを使うのも有効です。その手の既成コンテナはだいたい Webmin がくっついていて非常に楽です。\nまた、今回は Docker Compose を使いましたが、単一コンテナを扱いたいだけなのであれば、Makefile を利用することもできそうです。Photon OS にも make はインストールできます。\n次回は vCSA 6.5 の導入を行います。\n","date":"2017-03-20T17:11:17Z","image":"/archives/2466/img/DSC06327.jpg","permalink":"/archives/2466/","title":"Docker 上で bind を動かす"},{"content":"Photon OS を使いたい 先のエントリ で以下のように書きました。\nとりあえず vSphere 6.5 の環境をざっと触ってみたいので、ひとまず vCSA を立てていろいろ様子をみて、あとは素直に小さな仮想化基盤としておもちゃにします。\nvCSA をお行儀よく（きちんと vCenter Server に FQDN を持たせる形で）立てたい場合は、DNS サーバがないとそもそもインストールができません。\n今回はせっかくなのでお行儀のよい構成にしたいのですが、DNS サーバが欲しいからといって適当に CentOS に bind を入れるというのは自分にとって何も新しいことがなくてつまらないので、どうせなら触ったことがないものを触りましょう。\nというわけで、引き続き VMware つながりで、今回は Photon OS を使ってみます。Photon OS は、VMware が公開している、コンテナホスト用の軽量 Linux ディストリビューションです。\nPhoton OS by VMware 同じ分野だと CoreOS とか CentOS Atomic Host とか、Snappy Ubuntu Core とか、そのあたりが有名です。今回のこの Photon OS は、VMware プラットフォーム上での動作に最適化されているとのことで、この点がおそらく類似のコンテナホスト用 OS との差別化要素になるのかと思われます。最近、VMware vSphere Integrated Containers という製品も GA になりましたし、VMware がこの分野に注力しているのは確かなようですね。\nちなみに Photon OS は、コンテナホスト用ではあるものの、実は vCenter Server Appliance 6.5（vCSA 6.5）のベース OS としても採用されています。vCSA 6.0 までは、Photon OS ではなく SUSE Linux Enterprise Server でした。\nさて、今回の目的は DNS サーバを立てることなので、Photon OS 上に bind を動かすための Docker コンテナを作ることを目指します。本エントリではひとまず、Photon OS をデプロイして、そこで何らかの Docker コンテナを動かす、というところまで進めます。\nOVA テンプレートのダウンロードとデプロイ 初回起動とパスワードの変更 キー配列の変更 IP アドレスと DNS の設定 ホスト名の変更 タイムゾーンと時刻同期の設定 ロケールの変更 一般ユーザの追加 Docker を動かす 以下、当然ですが執筆時点の情報なので、時間経過に伴って陳腐化する可能性があります。\nリソース 公式の Wiki が比較的充実しているので、そこを見ながら作業します。\nWelcome to the Photon OS Wiki ! 日本語のリソースでは、gowatana さんの VMTN のウェブログがおそらくいちばん詳しそうです。ただし、こちらは Photon OS をフルインストールした前提で記述されているので、ミニマルの構成では手順が不足する部分がある点は注意が必要です。\nにほんご VMware | VMware Communities photon_linux タグ、または photon タグで検索するとまとまった情報が得られます 今回は、ISO イメージからのインストールではなく、OVA テンプレートを利用して、ミニマル構成での構築を行います。\nOVA テンプレートのダウンロードとデプロイ ダウンロードページから OVA ファイルをダウンロードします。OVA テンプレートは、ミニマル構成のみの提供です。\nDownloading Photon OS 今回は、手元の環境が vSphere 6.5 なので、仮想ハードウェアバージョンが 11 のものを選びます。\nダウンロード後、vSphere 環境にインポートします。この部分は特に書くほどの手順でもないですが、Wiki にもガイドがあります。\nCreating a Photon OS VM by Importing the OVA 記載通り、パワーオンする前の仮想マシンをテンプレートに変換して保存しておくと、今後インスタンスを増やしたくなったときに便利かもしれません。\nAt this point, you’ve got a Photon OS instance ready to go; but before you power on that Photon OS instance, consider first converting this VM into a template. By converting this imported VM to a template, you have a master Photon OS instance that can be combined with vSphere Guest Customization to enable rapid provisioning of Photon OS instances.\n以降、文字ばかりになるので、せめてかわいいねこの写真でも載せておきます。\n初回起動とパスワードの変更 ネットワークアダプタを適切なポートグループに接続させることで、パワーオン後、IP アドレスが DHCP サーバから取得されます。あらかじめ open-vm-tools が導入されているので、付与された IP アドレスは vSphere Client から確認できます。root ユーザでの SSH 接続もデフォルトで許可されています。\nよって、起動後、ただちに SSH による接続が可能です。任意の SSH クライアントから root ユーザでログインして初期構築を進めます。初期パスワードは changeme です。\n初回ログイン後、root ユーザのパスワードの変更が求められるので、指示に従います。\n(current) UNIX password: New password: Retype new password: キー配列の変更 SSH で接続しての操作では通常は意識する必要はありませんが、トラブル時など、どうしても仮想マシンのコンソールで直接操作しなければならない状態に陥ることがあります。これに備えて、キー配列を日本語キーボードに合わせて変更しておきます。\nPhoton Linux で日本語キーボードを使用する。 実際の設定方法は上記が参考になりますが、ミニマル構成の場合、そもそもキー配列のライブラリが入っていません。\nroot@photon-machine [ ~ ]# localectl list-keymaps Couldn\u0026#39;t find any console keymaps. よって、キー配列のパッケージのインストールから始めます。\nroot@photon-machine [ ~ ]# tdnf install kbd Installing: kbd x86_64 2.0.3-2.ph1 3.30 M Total installed size: 3.30 M Is this ok [y/N]:y Downloading: kbd 1599403 100% Testing transaction Running transaction Complete! tdnf は、Photon OS のパッケージマネージャで、yum の後継である dnf の簡易版（Tiny DNF）です。/etc/yum.repos.d/ 配下の設定ファイルを読むとわかりますが、Photon OS はデフォルトで https://dl.bintray.com/vmware/ 以下のリポジトリを参照するように構成されています。\nさて、これで日本語キーボードの情報が使えるようになったので、あとは実際に割り当てて完了です。\nroot@photon-machine [ ~ ]# localectl list-keymaps | grep jp106 jp106 root@photon-machine [ ~ ]# localectl set-keymap jp106 root@photon-machine [ ~ ]# localectl System Locale: LANG=en_US.UTF-8 VC Keymap: jp106 X11 Layout: jp X11 Model: jp106 X11 Options: terminate:ctrl_alt_bksp 反映は即時です。この変更は恒久的に保持されます。\nIP アドレスと DNS の設定 続いて、DHCP による動的取得ではなく、静的 IP アドレスを利用するように構成します。\nSetting a Static IP Address Photon Linux の Network 設定変更について。（IP / DNS / Hostname…） 構成するインタフェイスは eth0 です。\nroot@photon-machine [ ~ ]# networkctl IDX LINK TYPE OPERATIONAL SETUP 1 lo loopback carrier unmanaged 2 eth0 ether routable configured 2 links listed. 基本的にドキュメント通り進めればよいです。systemd を利用した一般的な設定手順と同じです。\nまずは静的 IP アドレスを eth0 に付与するための設定ファイルを新しく作ります。設定ミスに備え、この段階ではまだ DHCP を明示的に有効にしておきます。\nroot@photon-machine [ ~ ]# vim /etc/systemd/network/10-static-eth0.network root@photon-machine [ ~ ]# cat /etc/systemd/network/10-static-eth0.network [Match] Name=eth0 [Network] DHCP=yes Address=192.168.0.249/24 Gateway=192.168.0.1 DNS=192.168.0.1 root@photon-machine [ ~ ]# chmod 644 /etc/systemd/network/10-static-eth0.network systemd.network は名前順に設定ファイルを読み込みますが、最初に [Match] の条件に一致した設定ファイルの内容以外は無視します。\nこのため、先に DHCP 設定が読まれてしまうと、静的 IP アドレスが付与されないことになってしまいます。このような事態を避けるため、もともと存在していた DHCP 設定用のファイルは、eth0 の設定のあとに読まれるよう、名前を変える必要があります。将来的にネットワークアダプタを追加した際の動きを考慮しないのであれば、そもそもファイル自体を消してしまっても構いません。\nroot@photon-machine [ ~ ]# mv /etc/systemd/network/10-dhcp-en.network /etc/systemd/network/20-dhcp-en.network この後、ネットワークサービスを再起動します。\nroot@photon-machine [ ~ ]# systemctl restart systemd-networkd 10-static-eth0.network に DHCP=yes を記載したことで、ここでネットワークサービスを再起動しても、もともと DHCP で付与されていた IP アドレスが保持されます（正確には DHCP サーバから同じ IP アドレスが再度割り当てられているだけです）。SSH のセッションが切断されることなく、操作を続行できます。\n以下のコマンドで、IP アドレスが 2 つ付与されていることが確認できます。\nroot@photon-machine [ ~ ]# ip addr 1: lo: \u0026lt;LOOPBACK,UP,LOWER_UP\u0026gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 00:50:56:87:dd:02 brd ff:ff:ff:ff:ff:ff inet 192.168.0.249/24 brd 192.168.0.255 scope global eth0 valid_lft forever preferred_lft forever inet 192.168.0.11/24 brd 192.168.0.255 scope global secondary dynamic eth0 valid_lft 86334sec preferred_lft 86334sec inet6 fe80::250:56ff:fe87:dd02/64 scope link valid_lft forever preferred_lft forever 上記のように正常に静的 IP アドレスが付与されていることが確認できたら、新しい IP アドレスに SSH で接続しなおします。\nその後、改めて 10-static-eth0.network を編集し、DHCP を無効化したあと、再度ネットワークサービスを再起動し、IP アドレスが一つだけになったことを確認します。\nroot@photon-machine [ ~ ]# vim /etc/systemd/network/10-static-eth0.network root@photon-machine [ ~ ]# cat /etc/systemd/network/10-static-eth0.network [Match] Name=eth0 [Network] DHCP=no Address=192.168.0.249/24 Gateway=192.168.0.1 DNS=192.168.0.1 root@photon-machine [ ~ ]# systemctl restart systemd-networkd root@photon-machine [ ~ ]# ip addr 1: lo: \u0026lt;LOOPBACK,UP,LOWER_UP\u0026gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 00:50:56:87:dd:02 brd ff:ff:ff:ff:ff:ff inet 192.168.0.249/24 brd 192.168.0.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::250:56ff:fe87:dd02/64 scope link valid_lft forever preferred_lft forever これで、安全に DHCP による動的取得から静的 IP アドレスの利用へ切り替えられました。\nホスト名の変更 OVA テンプレートからデプロイした場合、ホスト名はデフォルトで photon-machine になっています。\nホスト名は hostnamectl コマンドで変えられます。特に目新しいことはありません。\nroot@photon-machine [ ~ ]# hostnamectl set-hostname kuro-ph01.kuro.local root@photon-machine [ ~ ]# hostnamectl Static hostname: kuro-ph01.kuro.local Icon name: computer-vm Chassis: vm Machine ID: 889b661d4b9446c48002b7ed596a00a4 Boot ID: 67367e5ce4094074be76a82f0a3c3a7b Virtualization: vmware Operating System: VMware Photon/Linux Kernel: Linux 4.4.41-1.ph1-esx Architecture: x86-64 反映は即時です。この変更は恒久的に保持されます。\nタイムゾーンと時刻同期の設定 以下のエントリによると、フルインストール構成ではいろいろと設定が必要そうです。\nPhoton Linux での NTP サーバとの時刻同期。（systemd-timesyncd / ntpd） が、OVA からデプロイした場合は、デフォルトで時刻同期が動いていました。Google の NTP サーバと同期しているようです。\nroot@photon-machine [ ~ ]# timedatectl Local time: Sun 2017-03-19 10:05:43 UTC Universal time: Sun 2017-03-19 10:05:43 UTC RTC time: Sun 2017-03-19 10:05:43 Time zone: UTC (UTC, +0000) Network time on: yes NTP synchronized: yes RTC in local TZ: no root@photon-machine [ ~ ]# systemctl status systemd-timesyncd -l ● systemd-timesyncd.service - Network Time Synchronization Loaded: loaded (/usr/lib/systemd/system/systemd-timesyncd.service; enabled; vendor preset: enabled) Active: active (running) since Sun 2017-03-19 06:22:48 UTC; 3h 42min ago Docs: man:systemd-timesyncd.service(8) Main PID: 128 (systemd-timesyn) Status: \u0026#34;Synchronized to time server 216.239.35.12:123 (time4.google.com).\u0026#34; Tasks: 2 CGroup: /system.slice/systemd-timesyncd.service `-128 /lib/systemd/systemd-timesyncd Mar 19 06:52:29 photon-machine systemd-timesyncd[128]: Synchronized to time server 216.239.35.8:123 (time3.google.com). Mar 19 09:37:34 photon-machine systemd-timesyncd[128]: Network configuration changed, trying to establish connection. Mar 19 09:37:34 photon-machine systemd-timesyncd[128]: Synchronized to time server 216.239.35.8:123 (time3.google.com). Mar 19 09:40:00 photon-machine systemd-timesyncd[128]: Network configuration changed, trying to establish connection. Mar 19 09:40:10 photon-machine systemd-timesyncd[128]: Timed out waiting for reply from 216.239.35.8:123 (time3.google.com). Mar 19 09:40:10 photon-machine systemd-timesyncd[128]: Synchronized to time server 216.239.35.12:123 (time4.google.com). Mar 19 09:40:12 photon-machine systemd-timesyncd[128]: Network configuration changed, trying to establish connection. Mar 19 09:40:12 photon-machine systemd-timesyncd[128]: Synchronized to time server 216.239.35.12:123 (time4.google.com). Mar 19 09:43:32 photon-machine systemd-timesyncd[128]: Network configuration changed, trying to establish connection. Mar 19 09:43:32 photon-machine systemd-timesyncd[128]: Synchronized to time server 216.239.35.12:123 (time4.google.com). ConditionVirtualization は !container になっていました。コンテナ環境内でなければ勝手に動くようです。\nroot@photon-machine [ ~ ]# grep ConditionVirtualization /lib/systemd/system/systemd-timesyncd.service ConditionVirtualization=!container というわけで、何もせず、タイムゾーンだけ変えます。\nPhoton Linux の時計を日本時間（JST）にする。 タイムゾーンはさすがにミニマル構成といえど入っていました。反映させるだけで完了です。\nroot@photon-machine [ ~ ]# timedatectl list-timezones | grep Asia/Tokyo Asia/Tokyo root@photon-machine [ ~ ]# timedatectl set-timezone Asia/Tokyo root@photon-machine [ ~ ]# timedatectl Local time: Sun 2017-03-19 19:11:09 JST Universal time: Sun 2017-03-19 10:11:09 UTC RTC time: Sun 2017-03-19 10:11:09 Time zone: Asia/Tokyo (JST, +0900) Network time on: yes NTP synchronized: yes RTC in local TZ: no root@photon-machine [ ~ ]# date Sun Mar 19 19:11:32 JST 2017 ロケールの変更 ロケールはデフォルトで en_US.UTF-8 になっています。このままでもよいのですが、せっかくなので ja_JP.UTF-8 に変更します。\nが、最初は何のロケール情報もありません。\nroot@photon-machine [ ~ ]# localectl list-locales 必要なロケール情報を自分で生成する必要があります。\nChanging the Locale で、上記ページには /usr/share/locale/locale.alias から言語を選んで /etc/locale-gen.conf に追記して locale-gen.sh を叩けと書いてありますが、実際はそもそも /usr/share/locale/locale.alias が存在しないですし、そのまま進めても怒られてしまいます。\nroot@photon-machine [ ~ ]# locale-gen.sh Generating locales... en_US.ISO-8859-1...locale alias file `/usr/share/locale/locale.alias\u0026#39; not found: No such file or directory というわけで、強引ですが空ファイルでよいので /usr/share/locale/locale.alias を作ってやると回避できます。これ、おそらく将来のリリースで修正されるかと。\n最終的には以下の手順です。言語は好きに読み替えてください。\nroot@photon-machine [ ~ ]# touch /usr/share/locale/locale.alias root@photon-machine [ ~ ]# echo -e \u0026#34;ja_JP\\t\\tUTF-8\u0026#34; \u0026gt;\u0026gt; /etc/locale-gen.conf root@photon-machine [ ~ ]# echo -e \u0026#34;ja_JP.UTF-8\\tUTF-8\u0026#34; \u0026gt;\u0026gt; /etc/locale-gen.conf root@photon-machine [ ~ ]# locale-gen.sh Generating locales... en_US.ISO-8859-1... done en_US.UTF-8... done ja_JP.UTF-8... done ja_JP.UTF-8... done Generation complete. root@photon-machine [ ~ ]# localectl list-locales en_US en_US.iso88591 en_US.utf8 ja_JP ja_JP.utf8 root@photon-machine [ ~ ]# localectl set-locale LANG=\u0026#34;ja_JP.UTF-8\u0026#34; root@photon-machine [ ~ ]# localectl System Locale: LANG=ja_JP.UTF-8 VC Keymap: jp106 X11 Layout: jp X11 Model: jp106 X11 Options: terminate:ctrl_alt_bksp 反映は即時です。恒久的に保持されます。\n一般ユーザの追加 いつまでも root なのはアレなので、一般ユーザを作ります。このユーザには Docker 操作用の特権を与えたいので、明示的に docker グループを作成して所属させます。sudo ができるように wheel にも所属させます。本当は一つのユーザに複数の役割は持たせるべきではないですが、よしとします。\nPhoton Linux への SSH ログイン。 root@photon-machine [ ~ ]# groupadd docker root@photon-machine [ ~ ]# useradd -m -g docker -G wheel kuro root@photon-machine [ ~ ]# passwd kuro New password: Retype new password: passwd: password updated successfully そして実はデフォルトでは sudo コマンドがないのでインストールします。\nroot@photon-machine [ ~ ]# tdnf install sudo Installing: sudo x86_64 1.8.15-3.ph1 3.47 M Total installed size: 3.47 M Is this ok [y/N]:y Downloading: sudo 1319187 100% Testing transaction Running transaction groupadd: group \u0026#39;wheel\u0026#39; already exists Complete! kuro ユーザで SSH で接続して、問題なければ root での直接ログインを禁止させます。PermitRootLogin を no にするだけです。OVA テンプレートからデプロイした場合は、デフォルトで yes になっています。\nここ以降、kuro ユーザで操作しています。\nkuro@kuro-ph01 [ ~ ]$ sudo vim /etc/ssh/sshd_config kuro@kuro-ph01 [ ~ ]$ sudo tail /etc/ssh/sshd_config Subsystem sftp /usr/libexec/sftp-server # Example of overriding settings on a per-user basis #Match User anoncvs # X11Forwarding no # AllowTcpForwarding no # PermitTTY no # ForceCommand cvs server PermitRootLogin no UsePAM yes kuro@kuro-ph01 [ ~ ]$ sudo systemctl restart sshd kuro@kuro-ph01 [ ~ ]$ sudo systemctl status sshd ● sshd.service - OpenSSH Daemon Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled) Active: active (running) since Sun 2017-03-19 19:52:39 JST; 9s ago Main PID: 1169 (sshd) Tasks: 1 CGroup: /system.slice/sshd.service `-1169 /usr/sbin/sshd -D Mar 19 19:52:39 kuro-ph01.kuro.local systemd[1]: Started OpenSSH Daemon. Mar 19 19:52:39 kuro-ph01.kuro.local sshd[1169]: Server listening on 0.0.0.0.... Mar 19 19:52:39 kuro-ph01.kuro.local sshd[1169]: Server listening on :: port 22. Hint: Some lines were ellipsized, use -l to show in full. Docker を動かす さて、ようやく本題です。が、もはやここまで来れば以降は Docker のごく一般的な操作の話なだけで特別なことはありません。\nInstalling a Containerized Application to Help Demonstrate Capability VMware Photon Linux の Install ～ Docker コンテナ起動。 Docker 自体はデフォルトでインストールされています。\nkuro@kuro-ph01 [ ~ ]$ docker -v Docker version 1.12.1, build 23cf638 自動起動は無効の状態です。起動させて、自動起動も有効にします。\nkuro@kuro-ph01 [ ~ ]$ sudo systemctl start docker kuro@kuro-ph01 [ ~ ]$ sudo systemctl status docker ● docker.service - Docker Daemon Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: enabled) Active: active (running) since Sun 2017-03-19 19:27:00 JST; 5s ago Docs: http://docs.docker.com Main PID: 618 (dockerd) Tasks: 6 CGroup: /system.slice/docker.service `-618 dockerd --containerd /run/containerd.sock Mar 19 19:27:00 kuro-ph01.kuro.local docker[618]: time=\u0026#34;2017-03-19T19:27:00.221740890+09:00\u0026#34; level=warnin...ght\u0026#34; Mar 19 19:27:00 kuro-ph01.kuro.local docker[618]: time=\u0026#34;2017-03-19T19:27:00.221854117+09:00\u0026#34; level=warnin...ice\u0026#34; Mar 19 19:27:00 kuro-ph01.kuro.local docker[618]: time=\u0026#34;2017-03-19T19:27:00.222276327+09:00\u0026#34; level=info m...rt.\u0026#34; Mar 19 19:27:00 kuro-ph01.kuro.local docker[618]: time=\u0026#34;2017-03-19T19:27:00.238793455+09:00\u0026#34; level=info m...lse\u0026#34; Mar 19 19:27:00 kuro-ph01.kuro.local docker[618]: time=\u0026#34;2017-03-19T19:27:00.297271226+09:00\u0026#34; level=info m...ess\u0026#34; Mar 19 19:27:00 kuro-ph01.kuro.local docker[618]: time=\u0026#34;2017-03-19T19:27:00.327413635+09:00\u0026#34; level=info m...ne.\u0026#34; Mar 19 19:27:00 kuro-ph01.kuro.local docker[618]: time=\u0026#34;2017-03-19T19:27:00.327488574+09:00\u0026#34; level=info m...ion\u0026#34; Mar 19 19:27:00 kuro-ph01.kuro.local docker[618]: time=\u0026#34;2017-03-19T19:27:00.327504134+09:00\u0026#34; level=info m...12.1 Mar 19 19:27:00 kuro-ph01.kuro.local systemd[1]: Started Docker Daemon. Mar 19 19:27:00 kuro-ph01.kuro.local docker[618]: time=\u0026#34;2017-03-19T19:27:00.333295270+09:00\u0026#34; level=info m...ock\u0026#34; Hint: Some lines were ellipsized, use -l to show in full. kuro@kuro-ph01 [ ~ ]$ systemctl enable docker Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service. 起動しました。適当なコンテナイメージを動かしてみます。適当に vmwarecna/nginx など。\nkuro@kuro-ph01 [ ~ ]$ docker run -d -p 80:80 vmwarecna/nginx Unable to find image \u0026#39;vmwarecna/nginx:latest\u0026#39; locally latest: Pulling from vmwarecna/nginx a3ed95caeb02: Pull complete b6f2388a20dd: Pull complete a305e4b888ce: Pull complete 80596a504ef3: Pull complete 99c028eff2a4: Pull complete a1cee46bc434: Pull complete 9bd9868012b9: Pull complete 6fa7100a2613: Pull complete Digest: sha256:f73bbae0f31823c06478b1fa5efb4957bc25239802fd5ea94e4442c0a6090d23 Status: Downloaded newer image for vmwarecna/nginx:latest 02e4fcf9b805a01b0d7bc203e2b78f655fa9aed2a7ff5a6abfbc93703b13a41e 勝手にリポジトリ（Docker Hub）からコンテナイメージを探してダウンロードして起動してくれました。簡単ですね。\nダウンロードしたコンテナイメージと起動状態は以下のように確認できます。\nkuro@kuro-ph01 [ ~ ]$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE vmwarecna/nginx latest c237cfda7789 23 months ago 93.48 MB kuro@kuro-ph01 [ ~ ]$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 02e4fcf9b805 vmwarecna/nginx \u0026#34;nginx -g \u0026#39;daemon off\u0026#34; 2 minutes ago Up 2 minutes 0.0.0.0:80-\u0026gt;80/tcp, 443/tcp hungry_stonebraker ブラウザで http://\u0026lt;IP アドレス\u0026gt;/ にアクセスして、以下のようにデモンストレーションのページが開けば成功です。\n動くのが分かったので、お掃除します。まずはコンテナの停止。\nkuro@kuro-ph01 [ ~ ]$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 02e4fcf9b805 vmwarecna/nginx \u0026#34;nginx -g \u0026#39;daemon off\u0026#34; 2 minutes ago Up 2 minutes 0.0.0.0:80-\u0026gt;80/tcp, 443/tcp hungry_stonebraker kuro@kuro-ph01 [ ~ ]$ docker stop 02e4fcf9b805 02e4fcf9b805 kuro@kuro-ph01 [ ~ ]$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 02e4fcf9b805 vmwarecna/nginx \u0026#34;nginx -g \u0026#39;daemon off\u0026#34; 5 minutes ago Exited (0) 8 seconds ago hungry_stonebraker 止めたらコンテナを消します。\nkuro@kuro-ph01 [ ~ ]$ docker rm 02e4fcf9b805 02e4fcf9b805 kuro@kuro-ph01 [ ~ ]$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES イメージも要らないので消します。\nkuro@kuro-ph01 [ ~ ]$ docker rmi c237cfda7789 Untagged: vmwarecna/nginx:latest Untagged: vmwarecna/nginx@sha256:f73bbae0f31823c06478b1fa5efb4957bc25239802fd5ea94e4442c0a6090d23 Deleted: sha256:c237cfda7789b89aaed30c646fb885e89e423b4adc708f3a02981f6c4d55aed3 Deleted: sha256:7df42bfffcdf0ef09a8a7e89df5a7c3e35d10bfa2eb39fb1a367b6fdad79f858 Deleted: sha256:4c2eef2ffbe175f3372463bf3a5223ca9a524360510f30814988a620ebfb0248 Deleted: sha256:db57fe254f4599d7aa2a949a608cb41d31a828aa15e6bd9c57e6e4785e10d224 Deleted: sha256:5dab3f8810166fa8a16f74aa4e71b8d658ad6e6ebfe6851509df386d55b02ac0 Deleted: sha256:e327d626ee3d33195a1efe565a9d98415b4ef4ddfcfa5757609f86a661c13ca6 Deleted: sha256:7d433cbce64586e009ebffefa71b27573a374f3b2e6d66252a96cea88d7ccd31 Deleted: sha256:992d02669b3c389cece05fee1714f81bd9694ba23aeedff75cd054611a4b5d4c Deleted: sha256:58647153590d6ca2ecc30e0ddb208b9b24743967ce925383df462ec710699cc7 Deleted: sha256:fd622e88e74de6d4b74f9e7bf684bac2238f59b929475f70f092beb6db6b61db Deleted: sha256:f835ef83693c18a2694bedb0ecaca78e968a4846242abae2b704db2ac7140f3f Deleted: sha256:5062638dae85cb1e1c79154b25b0e2d4518f6267742638256ec619d937f3183e Deleted: sha256:3911ed2f627c2ff8e453a86182f7f1570ad95595b443507bc3d7eb05f3c53f41 Deleted: sha256:affbb86305e946bf861b7ec77af0e66287eaf8649bb4522d718eeac6079d94b7 Deleted: sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef kuro@kuro-ph01 [ ~ ]$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE まとめ Photon OS を OVA テンプレートからデプロイし、必要な初期設定を行ったあと、Docker の動作確認を行いました。\n今回はここまで。あとはどうにでもなりますね。次回は Docker 上で bind を動かします。\n","date":"2017-03-19T17:51:24Z","image":"/archives/2433/img/WS000144.png","permalink":"/archives/2433/","title":"Photon OS をデプロイして Docker を動かす"},{"content":"新しいおもちゃ is Now Available Intel の NUC を買いました。第 6 世代のうちの、NUC6i5SYH です。\n製品の Web ページ を見るとそろそろ第 7 世代のものが買えるようになりそうな雰囲気がありますが、待つのが嫌なので買ってしまいました。\n使い道は考えていませんが、そういうときはとりあえず ESXi を入れます。新しいものが好きなので、現時点で最新の ESXi 6.5 にしました。\n注意 Intel NUC は VMware Compatibility Guide に乗っていないハードウェアです。何があって文句は言えません。\n買ったメモリとストレージも、Intel がテストした構成の一覧には記載のないデバイスです。\nSystem Memory for Intel® NUC Kits NUC6i3SY/NUC6i5SY [Tested Peripherals for Intel® NUC Kits NUC6i[x]SY][5] 買ったもの 列挙します。\nSSD はデータストアとして占有させたかったので、ESXi のブート用には USB メモリを別に用意します。\n本体 Intel NUC Kit NUC6i5SYH 第 6 世代 NUC の中で二番目に性能が高いモノ 一番性能が高いのは見た目がドクロで好みに合わないのでやめ…… メモリ Crucial 32GB Kit (2 x 16GB) DDR4-2133 SODIMM NUC6i5SYH で使える最大容量（2 x 16 GB）まで積む ストレージ（データストア用） Crucial MX300 525GB SATA 2.5″ 7mm (with 9.5mm adapter) Internal SSD 525 GB の 2.5 インチ SATA SSD M.2 は要らなくなった時の流用がしにくいのでやめた ストレージ（ブート用） SanDisk Cruzer Fit USB Flash Drive CSDCZ33-016G-J57 16 GB の USB メモリ ESXi 6.5 のインストール要件は 1 GB 以上なので大きすぎるけど、最近はもう小さいのが売っていない…… 全部で 9 万円くらいですね。いい時代です。\nインストール 何も考えずに製品の公式の ISO イメージをそのままインストールすれば問題なしでした。\nVMware vSphere Hypervisor (ESXi) 6.5.0 VMware-VMvisor-Installer-6.5.0-4564106.x86_64.iso 昔は非公式のドライバを自分で突っ込まないとうまくデバイスが認識されなくてダメだったようですが、最近は公式のそのままで大丈夫みたいですね。\nセットアップ インストール後、そのまま DCUI で IP アドレスやらホスト名やらを設定したら、vSphere Client で ESXi につないで必要な設定をします。\nvSphere Client は、C# 版はもうないので、HTML5 版を使います。ESXi に組み込みで用意されていて、ブラウザで ESXi に設定した IP アドレスに接続するだけで使えます。快適です。\nトラブルと対処 さて、ESXi のインストールは何の問題なく終了したものの、データストアのアクセスが異常に遅いというトラブルが発生しました。\nESXi 6.5 になって追加された新しいデフォルトの AHCI ドライバが、NUC の SATA コントローラをうまくハンドルできなかったみたいです。\n以下のようにして明示的に vmw_ahci ドライバを無効化することで解決できました。\nesxcli system module set -e=false -m=vmw_ahci このトラブル、具体的には、以下のような症状です。\nI/O のレイテンシが 1,000 ms 以上ある パフォーマンスのグラフの [最大待ち時間] が異常な数値 イベントログに以下のようなエントリがある 接続の問題により、ボリューム XXXXX へのアクセスが失われました。回復処理が進行中です。まもなく結果が報告されます。 接続の問題が発生後、ボリューム XXXXX へのアクセスがリストアされました。 vmkernel.log に以下のようなエントリがある WARNING: NMP: nmp_DeviceRequestFastDeviceProbe:XXXXX: NMP device “XXXXX” state in doubt; requested fast path state update… Cmd(0xXX) 0xXX, CmdSN 0xXX from world XXXXX to dev “XXXXX” failed H:0x2 D:0x0 P:0x0 Invalid sense data: 0xXX 0xXX 0xXX Aborting txn calleriD: 0xXX to slot XX: File system timeout (Ok to retry) このログ、SAN 構成なら目にすることはありますが、DAS のストレージでは通常は出ません。真っ先に SSD の初期不良を疑い、そして NUC の初期不良も疑いましたが、調べてみると同じ症状の事例が海外で複数報告されていました。解決策も載っています。\nVSphere / ESXi 6.5 AHCI performance problems wi… | VMware Communities Lost access to volume … due to connectivity issues | VMware Communities Very slow speed on SSD | VMware Communities ESXi 6.5 vmw_ahci SSD extreme high latency and vm freeze issues - vDrone NXHut - IT and Windows - News: Fix slow disk performance (vmw_ahci driver) in ESXi 6.5 その他 今回は問題にはなりませんでしたが、ESXi 6.5 では USB メモリもトラブルが起きがちのようです。\nこれも AHCI のドライバと同様、デフォルトのものが置き換わったことが原因だとか。KB 化もされていました。\nImportant information about the new ESXi 6.5 USB driver vmkusb, and the legacy USB drivers Upgrade to ESXi 6.5, installer doesn\u0026rsquo;t find USB… | VMware Communities ドライバ周りはトラブルが起きるとハード障害との切り分けがしにくくて厄介ですね。\n今後 とりあえず vSphere 6.5 の環境をざっと触ってみたいので、ひとまず vCSA を立てていろいろ様子をみて、あとは素直に小さな仮想化基盤としておもちゃにします。\n","date":"2017-02-27T15:03:11Z","image":"/archives/2420/img/DSC05966.jpg","permalink":"/archives/2420/","title":"Intel NUC に ESXi 6.5 を入れる"},{"content":"自宅の PC の HDD を SSD に換装しました。このツイートをしたあとに勢いでぽちった感じです。いつまでもアンポンタンなのは嫌ですからね。\nおうちの PC をそろそろ新調したいので Z170 + Skylake か X99 + Haswell-E とで迷ってるなうなんだけど、その前にまず HDD を SSD にしてボトルネックを I/O から CPU に寄せろやこのアンポンタンって感じもしてるなうなの\n\u0026mdash; くろい (@kurokobo) May 3, 2016 本業が本業だけに、いまだに自宅に SSD が一本もないというと驚かれることもしばしばあったのですが、そんな状況ともようやくおさらばです。\nとはいえ、データ用の HDD までぜんぶ SSD にするメリットもあまりないので、ひとまずは C ドライブだけ。中身をまるごとクローンして物理的に置き換えたので、純粋にディスク種別の差異だけを抽出できる状態が得られたことになります。\n体感でも起動速度をはじめ随所の挙動が目に見えて速くなったので、正直、定量的に比較するまでもなく圧倒的な効果が得られました、という結論はゆるがないのですが、そんなわけで、せっかくですしもうすこし細かく違いを見ることにします。\n写真は撮らなかったので、おいしかったごはんの写真をところどころに挟んでおきます。\n買ったもの Crucial の CT240BX200SSD1 です。\n対象の PC @Sycom の 2008 年くらいのモデル、GZ1000P45 です。\nSATA 3.0 が出る前なので、SSD を挿したところで SATA 2.0（3 Gbps）でしか動かないのです。しかたないのです。CPU は Intel Core 2 Quad Q9450 でした。\nOS は 2011 年の Windows 7 のクリーンインストール以来、そのまま使い続けているので、相応にもっさりしています。\nHDD は 300 GB で、140 GB くらいが使用済みです。\n行わせる処理 ベンチマークを走らせても数字的なうれしさしか得られないので、実運用を考えて、『PC を起動させる』ときの様子を観察することにしました。\n具体的には、停止状態から電源を入れて、OS が起動して安定するまでの処理の時間とパフォーマンス特性を比較します。ログイン後の処理の様子も観たかったので、ログイン画面が表示されたら直ちに（人力で）ログインするようにします。\n起動の完了は、\nディスク I/O CPU 使用率 のふたつで（人力で）判断するものとします。つまり、グラフが横ばいで推移しはじめたら完了、ということです。\n判断の根拠には、Windows の標準のパフォーマンスモニタを使います。カスタムデータコレクタセットを作成して、タスクスケジューラから OS の起動毎にこれをキックさせるように仕込みました。\nこの状態での PC の起動を、HDD の場合と SSD の場合とでそれぞれ 2 回行います。\n細かいことをいうと、そもそも HDD の場合と SSD の場合とで『電源を入れてからパフォーマンスモニタがキックされるまで』の時間がだいぶ違うはずですが、無視できることにしました。\nまとめ 数値上、以下の改善が見られました。\nディスク 所要時間 平均 CPU 使用率 平均ディスク使用率 平均応答時間 平均キュー長 平均帯域幅 平均 IOPS HDD 12:30 18.0 % 99.4 % 35.4 ms 6.0 5.4 MiB/s 216.4 SSD 3:35 32.3 % 26.8 % 1.0 ms 0.4 7.5 MiB/s 363.2 CPU やディスクの使用率、キュー長や応答時間から、HDD のときは典型的な I/O ボトルネックであったことが読み取れます。\nSSD にしたことで、I/O の応答性が上がり、CPU がきちんと使われるようになったようです。\n以下、グラフです。なお、先の記述通り HDD と SSD でそれぞれ 2 回計測していますが、似たようなグラフが 2 個できただけだったので、以下ではそれぞれの 1 回目の情報のみ掲載しています。そして SSD 分は起動が完了してしばらくした段階で計測を止めた関係で、グラフが途中までで切れています。\nCPU 使用率 25 % くらいで横ばいになったときが起動完了のタイミングです。\nHDD のときは CPU がロクに使えていないことがわかります。\nディスク使用率 ディスク使用率は、“100 - % Idle Time” で算出した値です。HDD には余裕がまるでありません。SSD はスパイクで 90 % くらいまで上がりはするものの、まだ余力がありそうです。\n帯域幅 劇的なちがい、とはいえないですが、平均でいえば上がっています。\n起動処理なのでやはり I/O は読み取りが中心のようです。\n帯域幅と時間を掛けると総転送量が求められるはずですが、HDD だと 4 GB、SSD だと 1.5 GB くらいになるのがちょっと謎です。\n同じシステムを起動させているなら総転送量も同じくらいになりそうなものですが、パフォーマンスモニタがキックされる前に大半が読み取り済みだったとか、短期間に I/O が集中したことでキャッシュヒット率が上がった（不要ページとしてキャッシュから破棄される前に再利用されてディスクへのアクセスが発生しなかった）とか、なにかあるのかもしれないです。\n応答時間 HDD のひどさがすごいです。三桁にまで行くと、馬鹿にされている気さえしてきます。\nSSD は優秀です。ほとんどの期間が 1 ms 以下でした。\nキュー長 非 RAID 構成なので、単一ディスクに対するキュー長です。通常は 2 以下であるべきなので、HDD は待ちが発生しすぎですね。\nSSD はほとんどが 1 以下でした。待たせずにさばけているようです。\nIOPS SSD で 2,500 くらい出ているのは納得ですが、HDD でも IOPS が 750 以上出ているというのが信じられないですね。このカウンタは正しいのでしょうか……。\nブロックサイズ これはこれ単体ではあまり意味のあるグラフではなさそうです。\nおわりに IOPS と応答時間の相関とか、ブロックサイズと応答時間の相関とか、おもしろい傾向が出てくることを期待して散布図を吐いてみたものの、有意な結果にはなりませんでした。相関が見たいなら、それこそベンチマークツールなどで負荷をかけて、そもそものワークロードを安定させないと、あまり意味がないですね。\nなにはともあれ、ディスクがボトルネックであることを確かめる前に SSD を買ったわけですが、ねらい通り速くなってくれてよかったです。ディスクがサチらなくなったので、M/B と CPU をよいものにすればもう少し早くなりそうですね。\nよいころあいなので、PC を買い替えがてら、いい加減 Windows 10 にしておきたい気もしています。起動が遅いならクリーンインストールするのがいちばん確実です。\n","date":"2016-05-07T08:20:59Z","image":"/archives/2392/img/DSC02003.jpg","permalink":"/archives/2392/","title":"HDD を SSD にした話"},{"content":"だいたい一年前の コレ を最後にコンサートのまとめエントリ的なものを書かなくなってしまっていました。\n単にそれどころではなくなった、というだけですが、中途半端なのも気持ち悪いからせめて 2015 年分はメモを残しておきます。\nソニー吹奏楽団 第 51 回定期演奏会 6 月 27 日（土）練馬文化センター 大ホール libertas ライブ vol. 6 “ピアソラ × 日本の歌” 6 月 14 日（日）MFY サロン ARTE TOKYO 第 5 回定期公演 6 月 21 日（日）東京オペラシティコンサートホール タケミツメモリアル 某社某イベント 8 月 19 日（水）都内某所 全国学校ギター合奏コンクール 2015 8 月 22 日（土）東京芸術劇場 コンサートホール 向日葵ギターアンサンブルコンサート 8 月 29 日（土）和光大学ポプリホール鶴川 イ・ムジチ合奏団 10 月 20 日（火）紀尾井ホール イ・ムジチ合奏団 10 月 24 日（土）サントリーホール JAGMO 伝説の音楽祭 - 勇者たちの響宴 - 10 月 25 日（日）新宿文化センター 大ホール サウザンドまつり 11 月 1 日（日）サウザンドシティ 多目的ホール ソニー吹奏楽団 ファミリーコンサート 11 月 3 日（火）大田区民ホール アプリコ 第 5 回 岡上分館カフェコンサート 11 月 28 日（土）岡上分館 リード×シエナ ～リードイヤー・クライマックス！～ 12 月 12 日（土）東京オペラシティコンサートホール タケミツメモリアル ギタークリスマスコンサート 2015 12 月 20 日（日）杜のホールはしもと なんだかんだでいろいろ行っているっぽさがあるですね。\nイ・ムジチ合奏団の公演に 2 回行けたのはすごくよかったです。ぜんぜん違うプログラムの日を選んだのでまるごとオイシイ感じでした。JAGMO は相変わらずイケイケでした。\n2016 年は書きたくなったときだけ書くことにします。\n","date":"2016-05-04T14:08:40Z","image":"/archives/2303/img/DSC02105.jpg","permalink":"/archives/2303/","title":"行ったコンサートとか"},{"content":"ぜんぜん知らなかったのだけれど、ふと VMware PowerCLI Blog を見たら、VMware Labs の Onyx の Web Client 対応版、Onyx for Web Client のリリースの案内が出ていた。\nOnyx for the Web Client – VMware Labs System Requirements には vSphere Web Client 6.0 用と書いてある。6.0 の Web Client から 5.5 につなげば 5.5 でも使えそう（後方互換、あったよね？）。\nOnyx というのは、vSphere Client の操作を PowerCLI や C# や vCO 用の JS などのコードにしてくれるとても便利なもので、ぼくも三年前に『vSphere Client の操作を PowerCLI のコードにしてくれる VMware の Onyx がすごく便利』というエントリを書いている。\nこのエントリ、いまでもちょくちょくアクセスがあるのだけれど、この末尾にこんな一文を入れていた。\nvSphere 5.1 からは vSphere Client ではなくて vSphere Web Client が標準になってくるので、Onyx たんがアップデートしてくれないといずれ使えなくなる疑惑がある。\n結局、従来の C# 版クライアント用の Onyx も 2014 年までは継続的にアップデートされていて、なんだかんだで vSphere 5.5 の C# 版クライアントでも使えてはいる。とはいうものの、根本的に C# 版クライアントだとできないことも多いわけで。\nWeb Client 用のが出てくれないとそのうち困りそうだなあと、このときからつらつら考えてはいたのだけれど、無事にリリースされてくれてよかった。安心した。\n","date":"2015-11-03T11:00:09Z","image":"/archives/2333/img/0.jpg","permalink":"/archives/2333/","title":"VMware Labs の Onyx に Web Client 用が出ていた"},{"content":"Tera Team でログを取るときの “タイムスタンプ” オプションみたいなことを、OS のネイティブの機能でやりたいシーンがあった。一時的な人力監視とか作業の証跡とかで。\n以下、Windows の場合（PowerShell）と Linux の場合（bash）のそれぞれで。\nWindows の場合（PowerShell） ワンライナでやる こうする。\n\u0026lt;任意のコマンド\u0026gt; | foreach-object {(get-date -f \u0026#34;[yyyy/MM/dd HH:mm:ss.ff] \u0026#34;) + \u0026#34;$_\u0026#34;} コマンドによっては表示が崩れるので、その場合は Out-String -Stream を通してやるとだいたいうまくいく。\n\u0026lt;任意のコマンド\u0026gt; | out-string -stream | foreach-object {(get-date -f \u0026#34;[yyyy/MM/dd HH:mm:ss.ff] \u0026#34;) + \u0026#34;$_\u0026#34;} 結果、こうなる。\nPS\u0026gt; ping 8.8.8.8 -t | foreach-object {(get-date -f \u0026#34;[yyyy/MM/dd HH:mm:ss.ff] \u0026#34;) + \u0026#34;$_\u0026#34;} [2015/11/02 01:04:43.31] [2015/11/02 01:04:43.32] 8.8.8.8 に ping を送信しています 32 バイトのデータ: [2015/11/02 01:04:43.32] 8.8.8.8 からの応答: バイト数 =32 時間 =7ms TTL=56 [2015/11/02 01:04:44.32] 8.8.8.8 からの応答: バイト数 =32 時間 =9ms TTL=56 [2015/11/02 01:04:45.32] 8.8.8.8 からの応答: バイト数 =32 時間 =4ms TTL=56 [2015/11/02 01:04:46.32] 8.8.8.8 からの応答: バイト数 =32 時間 =12ms TTL=56 関数にする 関数にする場合は、例えばこんな感じ。\nfunction add-timestamp { [cmdletbinding()] param ( [parameter(valuefrompipeline = $true, mandatory = $true)][psobject] $object, [string] $format = \u0026#34;[yyyy/MM/dd HH:mm:ss.ff] \u0026#34;, [switch] $trim ) process { $object | foreach-object { if ($trim) { (get-date -f $format) + \u0026#34;$_\u0026#34;.trimend() } else { (get-date -f $format) + \u0026#34;$_\u0026#34; } } } } -format（-f でもよい）で任意のフォーマットを指定できる。何も指定しなければデフォルト（“[yyyy/MM/dd HH:mm:ss.ff] ”）。\n-trim（-t でもよい）は、行末の不要な空白を削除するオプション。なにかの結果を format-table してから add-timestamp に渡すと、行バッファがあふれて折り返されて、全行間に空行が入って見えることがあるので、これを見掛け上抑止するためのもの。\n使うときはこんな感じ。ワンライナの場合と同様、場合によっては out-string -stream を噛ませた方がよさそう。\n\u0026lt;任意のコマンド\u0026gt; | add-timestamp \u0026lt;任意のコマンド\u0026gt; | add-timestamp -f \u0026#34;\u0026lt;HH:mm:ss\u0026gt; \u0026#34; \u0026lt;任意のコマンド\u0026gt; | add-timestamp -t \u0026lt;任意のコマンド\u0026gt; | out-string -stream | add-timestamp 結果、こうなる。\nPS\u0026gt; ping 8.8.8.8 -t | add-timestamp [2015/11/02 01:16:18.65] [2015/11/02 01:16:18.65] 8.8.8.8 に ping を送信しています 32 バイトのデータ: [2015/11/02 01:16:18.66] 8.8.8.8 からの応答: バイト数 =32 時間 =4ms TTL=56 [2015/11/02 01:16:19.66] 8.8.8.8 からの応答: バイト数 =32 時間 =4ms TTL=56 [2015/11/02 01:16:20.66] 8.8.8.8 からの応答: バイト数 =32 時間 =5ms TTL=56 [2015/11/02 01:16:21.66] 8.8.8.8 からの応答: バイト数 =32 時間 =8ms TTL=56 Linux の場合（bash） ワンライナでやる こうする。\n\u0026lt;任意のコマンド\u0026gt; | awk \u0026#39;{print strftime(\u0026#34;[%Y/%m/%d %H:%M:%S] \u0026#34;) $0}\u0026#39; strftime() はミリ秒単位の表示ができないっぽい。date で代替もできなそう ((やってみたら全行のタイムスタンプが同じ時刻になってダメだった))だし、しかたない。\n結果、こうなる。\n[kuro@localhost ~]$ vmstat 1 | awk \u0026#39;{print strftime(\u0026#34;[%Y/%m/%d %H:%M:%S] \u0026#34;) $0}\u0026#39; [2015/11/01 05:04:04] procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu----- [2015/11/01 05:04:04] r b swpd free buff cache si so bi bo in cs us sy id wa st [2015/11/01 05:04:04] 1 0 0 483204 23324 363936 0 0 145 2 32 44 0 1 99 0 0 [2015/11/01 05:04:05] 0 0 0 483172 23324 363960 0 0 0 0 26 36 0 0 100 0 0 [2015/11/01 05:04:06] 0 0 0 483172 23324 363960 0 0 0 0 26 36 0 0 100 0 0 [2015/11/01 05:04:07] 0 0 0 483172 23324 363960 0 0 0 0 27 42 0 0 100 0 0 関数にする 関数にする場合は、例えばこんな感じ。\n#!/bin/bash function add-timestamp () { f=\u0026#34;[%Y/%m/%d %H:%M:%S] \u0026#34; for opt in \u0026#34;$@\u0026#34;; do case \u0026#34;$opt\u0026#34; in \u0026#39;-f\u0026#39; | \u0026#39;-format\u0026#39; ) f=\u0026#34;$2\u0026#34; ;; esac done cat - | awk \u0026#34;{print strftime(\\\u0026#34;$f\\\u0026#34;) \\$0}\u0026#34; } -format（-f でもよい）で任意のフォーマットを指定できる。何も指定しなければデフォルト（“[%Y/%m/%d %I:%M:%S] ”）。\nこうやってつかう。\n\u0026lt;任意のコマンド\u0026gt; | add-timestamp \u0026lt;任意のコマンド\u0026gt; | add-timestamp -f \u0026#34;\u0026lt;%I:%M:%S\u0026gt; \u0026#34; 結果、こうなる。\n[kuro@localhost ~]$ vmstat 1 | add-timestamp [2015/11/01 05:18:48] procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu----- [2015/11/01 05:18:48] r b swpd free buff cache si so bi bo in cs us sy id wa st [2015/11/01 05:18:48] 2 0 0 482800 23648 364060 0 0 110 2 30 42 0 0 99 0 0 [2015/11/01 05:18:49] 0 0 0 482768 23648 364060 0 0 0 0 33 46 0 1 99 0 0 [2015/11/01 05:18:50] 0 0 0 482768 23648 364060 0 0 0 0 27 38 0 0 100 0 0 [2015/11/01 05:18:51] 0 0 0 482768 23648 364060 0 0 0 0 23 37 0 0 100 0 0 どうにかしてミリ秒出せないかな、これ。\n","date":"2015-11-02T11:00:11Z","image":"/archives/2312/img/add-timestamp.jpg","permalink":"/archives/2312/","title":"PowerShell と bash で任意のコマンドの出力の全行頭にタイムスタンプを付ける"},{"content":"Rubinetto で使っているウクレレベースのジャックが接触不良気味だったので交換。\nウクレレベースは KALA の UBASS-SMHG-FS というモデル。これについているピックアップシステムは、Shadow の SH NFX EQ-T UK というものらしい。\nジャックは金属の弾性を利用してプラグの固定と導通の確保を同時に実現する構造なので、極の曲がりっぷりが弱くなるとユルくなって導通も死ぬ。特に何もしなくても、経年劣化で死ぬ。\n保証期間内であれば販売店持ち込みが楽だけれど、すでに過ぎていたし、難しい話でもないので自分で。\n外す 本体の裏には作業用の穴がある。蓋は磁石での固定なのですぐ外せる。\nジャックは内と外の両側からのナットの締め付けで固定されているので、どちらかのナットを外して抜き取る。\n調べる 元の配線がわからないと部品の交換ができないのだけれど、インタネットでは配線図が見つけられなかったので実物を基に書き起こした。\n模式的に描くとこう。\n似非回路図っぽくするとこう。いろいろと省略しているけれど。\nバッテリとプリアンプのグラウンドが両方ともジャックのスリーブにつながっているのがめずらしいけれど、シールドを挿さない状態（リングとスリーブがショートしない状態）でもチューナ機能を使えるようにするためかな、たぶん。\nチューナ側にも別にスイッチがあるから、きっとそういうことだと思う。\n交換する 配線がわかれば、あとは新しいジャックを同じ配線で繋ぐだけ。今回買ったのは SCUD の EP=JACK2。\n最初にブッシングを通さないと詰む。ありがちなミスだけれどまじで実際やらかした。\nあとは適当に熱収縮チューブで絶縁しながら繋いでいく。\n繋ぎ終わったらこの時点でいちど音を出してみた。問題なし。\nあとは取り付け。\n板の厚みを適当に読んで内側のナットの位置を決めたら、外側から締め付けてやる。\n内側のナットが外側すぎると、シールドが挿し込みきれなくなって接触不良につながるので気をつける。内側のナットの位置はとてもだいじ。\n固定できたらブッシングも締め付けて、外側にキャップをつけて完了。\nおわり。\n","date":"2015-06-16T12:00:50Z","image":"/archives/2181/img/DSC01614.jpg","permalink":"/archives/2181/","title":"ウクレレベースのジャックを交換する"},{"content":"インストゥルメンタルバンド、(tys)2。\n多摩のギター部出身のひとたちも多く所属していることもあって、前から気にはなっていたのだけれど、遂に初ライブを敢行するというものだから行ってきた。\n6 月 3 日、火曜日。HEAVEN\u0026rsquo;S ROCK さいたま新都心 VJ-3 にて。\nその昔、野良音響屋さんとして PA をやっていたころは、こういう爆音は当たり前だったけれど。\nそういうところには最近久しく身を置いていないから、だから声を張り上げないと会話もままならない音圧の空間に居ること自体が、とても懐かしかった。\n演奏はキレイめで、想像していたよりもずっとオトナっぽい。\n軽音楽系だとノリとイキオイでエイヤっとやってしまうことが多そうだけれど、そこはしっかり造り込んでいたようで。\n演奏者側と PA 側との音量の制御がもうちょっと噛み合うとよかったかも。\nそれでも、知った顔がたのしそうにがんがん音楽しているようすを見るのは、見ているこっちもたのしいから、たのしい。\n次のライブはもっと近いところだそうで。時間があけば行ってみよう。\n","date":"2015-06-15T12:00:25Z","image":"/archives/2222/img/DSC01942.jpg","permalink":"/archives/2222/","title":"ORANGE MUSIC inc. presents Lightning Showcase"},{"content":"UnisOno さん主催、五月雨ギターアンサンブルコンサート。\n近隣のギターアンサンブル団体を集めて合同でひとつのコンサートをつくる企画で、“五月雨” 以外にもこれまでに “風待ち” とか “虹晴れ” とか、そういう名前で開催されてきた実績のある、シリーズめいたコンサート。\nこの日の出演は、Lluvia と、L\u0026rsquo;avenir さんと、Rubinetto の三団体。ぼくはこのうち Rubinetto と Lluvia ((このコンサートのために特別に編成されたギターアンサンブル団体。ぜんぶで七人。))の二団体にまたがって出演してきた。\n5 月 30 日、土曜日。相模原南市民ホールにて。\n朝早くからリハーサルだったのと、クラシックスタイルの Lluvia といつも通りのお気楽な Rubinetto とのテンションの違いも重なって、体力ががんがん持っていかれたけれど。\nいやはや、たのしかった。\nギターのたのしさ、というか、音楽のたのしさの本分って、ぼくにとってはどうしたって “本番” にある。\n自分ひとりで演奏していても、あるいは団体で閉じた空間で演奏していても、それはそれでもちろんたのしいのだけれど、結局のところ承認欲求のカタマリのような生き方をしているぼくには、ひとまえで演奏して反応をもらうことこそが、ぼくにとっての自己満足のいちばんの達成手段。そうして弾いているときの自分のテンションが、ぼくは好き。\n暴言が過ぎるかもしれないけれど、感覚でいえばそういうところが多分にあるのよね。\nもちろん、“本番” は “お客さん” あってこそのもので、だからお客さんを呼び込むために、訴求力を高める努力（≒練習）はしないといけないのは、忘れてはいけない事実だけれど。\n本番がたくさんあればいいなあと、だからぼくはそう思っていて、だから Rubinetto のフットワークの軽さがとても好き。\nそういう意味では、Lluvia のような “その場限り” の特別編成も、気軽にあちこちでできるといいのだけれど、なかなか機会がないのよねえ。\nL\u0026rsquo;avenir さんの演奏はあまり聴けなかったけれど…… そういえばコンクール以外では顔を合わせるのは初めてだったかもしれない。結成当初から見ていたけれど、人数もしっかり増えて、団体としてだいぶ安定してきた感があって。\nギター部がない学校の生徒さんたちがギター合奏を続ける道ってあまり多くないから、ながく続いて欲しいととても思う。\nさてさて、八月には今度は “向日葵” の名を冠して 向日葵ギターアンサンブルコンサート が開催されるので、こちらもぜひ。Rubinetto として出演予定。\n","date":"2015-06-14T12:00:45Z","image":"/archives/2220/img/DSC01762.jpg","permalink":"/archives/2220/","title":"五月雨ギターアンサンブルコンサート"},{"content":"所沢シティギターアンサンブルさんの、第 2 回演奏会。5 月 24 日、日曜日。所沢市民文化センターミューズの中の、キューブホールにて。キューブホール、響きも造りもギター向きでさいこうね。\nこの演奏会、もともと聴きにいくつもりだったのだけれど、何故だか依頼を受けたので、今回は野良映像屋さんとしてお手伝い。\nリハーサルからカメラをいじりつつ眺めていたのだけれど、ギターのギターらしい芳醇な響きを久しぶりに聴いたなあと思える、そういうステキな時間だった。\n成熟した技術と感性に裏打ちされたオトナのギターアンサンブルには、音に身を預けられる安心感と、抱擁感めいた心地よさがある。\nアンサンブル経験も豊富で、かつ独奏でもばりばり弾けてしまう技術をもったひとたち、の集まりなのだから、ギターの鳴らし方と響かせ方なんてウマいに決まっているわけではあるのだけれど。\nとくにプライムギターの音がたまらなかった。とろける。\n学生のエネルギッシュな演奏も好きではあるものの、三月四月五月と定期演奏会が続いて体力を奪われていたこともあって、だからぼくにとっては、とてもよい癒しの時間になった。\n今後も追いかけていきたい団体のひとつ。いつまで追いかけられるかは分からないけれど……。\n","date":"2015-06-13T12:00:00Z","image":"/archives/2218/img/DSC01666.jpg","permalink":"/archives/2218/","title":"第 2 回 所沢シティギターアンサンブル演奏会"},{"content":"さいたまダービー最終戦こと、大宮高校ギター部さんの定期演奏会。5 月 6 日、水曜日。さいたま市民会館大宮の大ホール。\nアンコールの出来が！ 至高でした！ そういえば 去年 もそうでしたね。\nメイン曲のように緻密にかちっと組み立てる作り方もできて、アンコールのように自由闊達な作り方もできる、そういうフレキシブルな器用さがありそうです。\n今回は全体的に前者寄りでしたが、個人的には後者の空気も大好きなので、積極的に登用してほしいところ。\nじっくり煮込んだ重奏も、じっくり煮込んだだけあってじっくり煮込んだ味がしました。\nなかでも Summer のうたいあげが絶妙で、久しぶりによいギターの音を聴いたなあと思える、納得の響きでした。よいものです。\nたのしかったです。\nぼくがこうしていろいろな部活の演奏会に足を運べるのも、いつまでできるのかなあって、最近そんなことを思うことが増えました。\n時間が経つのは、はやいものです。\n","date":"2015-06-12T12:00:57Z","image":"/archives/2191/img/DSC01595.jpg","permalink":"/archives/2191/","title":"大宮高校ギター部 第 40 回定期演奏会"},{"content":"坂戸高校ギター部さん。去年文化祭に行って 気になったので、今度は定期演奏会に行ってきました。\n5 月 4 日。会場は坂戸市文化会館の大ホール。\n文化祭のときから感じていたことですが、寸劇など “遊び” のテンションを全体に取り入れながらも、根底はひじょうに礼儀ただしい、とてもていねいな部活である印象を受けました。\n重奏では個々人の名前がアナウンスされて、入場時に一人ずつにスポットが当たって礼をして拍手をもらう時間がある。定期演奏会は『部活』という単位での催しで、だから個々人が大きく取り上げられることは多くないのですが、こういう枠をうまく使うのはすてきな文化ですね。\n演奏もとてもていねいで、逆にいえば全体的におとなしい印象もありましたが、HERO のトレモロやアラジンのグロッケン、流星群の導入部など、ところどころヒカるモノがあって満足でした。とくに流星群の導入部は空気感がさいこうによくて、ああいう音は聴くとぞくぞくしますね。演奏の最大の演出は音そのものだよなあと。音を出した先の空気までつくれると、演奏の世界はやっぱり広がります。\nただ、編成上、プライムギターが大多数なので、どうしても中音部が厚くなりがちな傾向はありそうです。プライムギターの音の丸さは武器ですが、同時に埋もれる原因にもなるので、役割に応じて音色を変化させるなどバランスを取ってみるのも楽しいかもですね。\nマイクがにょきにょき潤沢に立っていた（うらやましい！）のと、あとは重奏の方々の演奏にぴったりな衣装と照明と舞台づくりのセンス（うらやましい！）がうらやましいポイントでした。演出は曲の雰囲気に合いすぎていてズルいレベルです。\n演奏だけを追求した演奏会ではなくて、部活というコミュニティそのものを紹介する場、あるいは保護者の方々や OB の方々に成長をお披露目する場として、とても機能的な演奏会でした。よいエンタテイメントです。みなさんたのしそうです。\n30 年以上続いている長い歴史があるとのこと。これからも折に触れて追いかけて行きたいですね。\n","date":"2015-05-26T03:00:16Z","image":"/archives/2184/img/DSC015811.jpg","permalink":"/archives/2184/","title":"坂戸高校ギター部 第 31 回定期演奏会"},{"content":"行ってきた。4 月 19 日、土曜日。相模女子大学グリーンホールの、大ホール。伝説的なセンパイたる一期生さんたちの去年の活躍を受けての、二期生さんたちの定期演奏会。\n今年もしっかりとよい演奏会だった。『一期生スゴい』から『この部活スゴい』にきっちり切り替わったし、きっと来年も再来年もよい演奏会になるのだろうと思う。\nもちろん全曲がカンペキってことはなかったけれど、ウマい曲のウマい部分は信じられないくらいウマいのもあいかわらずで。コンクール曲であるムソルグスキィの本気で狙ってきている感とか、オーボエ協奏曲の一瞬で会場の空気を変えるほどの作り込みっぷりとか。さいこうでした。\nそして今年も指揮がよい。コンクール曲の彼も好きだけど、四年生の彼もまたスゴいのが出てきたなあという感じで、すでに来年の楽しみができてほくほくしている。\nコンクール、楽しみですね。8 月、東京芸術劇場で会いましょう。\n","date":"2015-05-02T02:27:22Z","image":"/archives/2165/img/DSC01503.jpg","permalink":"/archives/2165/","title":"相模原中等教育学校クラシックギター部 第 27 回定期演奏会"},{"content":"先日書いた 第 1 回 と 第 2 回 の Rubinetto 単独ライブ。ありがたいことにどちらも満員になり、そしてご好評をいただけたようです。\n録音・録画は両ライブともばっちり行っていましたが、この二回分を組み合わせて、当日のフルプログラムになるように全 17 曲、YouTube で公開しました。\nお越しいただけなかった方、ぜひどうぞ。お越しいただいていた方も、もう一度お楽しみいただければ幸いです。\n全曲入り再生リスト 全曲入りの再生リストです。再生ボタンをぽちっと押すと、17 曲ノンストップで連続で再生されます。BGM にいいですね！\n収録曲リスト 前述の再生リストは、以下の全 17 曲でできています。アンコールだけは 2 公演分どちらも入れてあるので重複。\n同じリストは、YouTube の再生リストのページ でも確認できます。\nBorn to Smile - 葉加瀬太郎 道草のススメ - ソノダバンド さんぽみち - Haracem だんご 3 兄弟 - 内野真澄 桜・咲くころ - 押尾コータロー きっとまたいつか - DEPAPEPE Carnival - 押尾コータロー Manic Street - ソノダバンド Moon River - H. マンシーニ to the Seashore - Haracem ストロマトライト - Haracem 二階建ての校舎 - Haracem Girls Talk - 葉加瀬太郎 翼 ～you are the HERO～ - 押尾コータロー Wild Stallions - 葉加瀬太郎 Born to Smile - 葉加瀬太郎（アンコール） Born to Smile - 葉加瀬太郎（アンコール） このうち “二階建ての校舎” は、第 2 回のみでの演奏でした。プログラムにも記載していなかった、とくべつな一曲です。\n作曲者である Haracem のオリジナル CD を参考音源に、“自分のパートの楽譜は自分で書き起こしててきとうにアレンジする” という試みのもとで練習が進められて、（ぼくを含む）メンバの強い希望でめでたく本プログラム入りをはたしました。よい曲です。\nオリジナル版は Haracem 自身の手でアップロード されています。\nプログラム こちらが当日配布したプログラムです。あわせてどうぞ。\n次のライブ予定 次は 5 月 30 日の土曜日、五月雨ギターアンサンブルコンサートに出演します。\n時間もたっぷり 40 分ほど。まだなにを弾くか決めていませんが、存分にお楽しみいただけるはずです。\nぜひどうぞ！\n団員もあいかわらず募集中です！ どしどしご連絡ください！\n","date":"2015-04-12T11:34:49Z","image":"/archives/2155/img/IMGP6410.jpg","permalink":"/archives/2155/","title":"Rubinetto 単独ライブの全曲を YouTube で公開しました！"},{"content":"先日の第 3 回から続いて、今回も Rubinetto にお声かけいただけたので出演してきました。3 月 29 日、日曜日。\n前回よりも規模が大きくなって、地域のおまつり、になっていました。\nあいかわらずまったりと平和な時間の流れる空間で、居心地がよいですね。あのゆったりとした空気は、やっぱりどことなくぼくのだいすきなあの高校に似ているのです。だからすき。\n演奏は三曲だけでしたが、のんびりと楽しめました。立ち見がわんさか出るほどの方々にお集まりいただけてありがたい限りです。\nスタッフのみなさま、ご来館いただいたみなさま、共演者のみなさま、ありがとうございました。そしてごちそうさまでした！\n動画はそのうち公開します。おたのしみに！\n","date":"2015-04-06T12:00:43Z","image":"/archives/2100/img/DSC01416.jpg","permalink":"/archives/2100/","title":"第 4 回 岡上分館カフェコンサート"},{"content":"毎度おなじみぼくの原点、多摩高校ギターアンサンブル部の、第 49 回定期演奏会に行ってきた。\n3 月 27 日、金曜日。多摩市民館大ホール。七年ぶりに “市民館” での開催。多摩市民館、ぼくの引退時（第 39 回）の開催地でもある。\nぽつぽつと感想ツイート（？）をしたので、それを転載する形で。\nぱとり重奏のクオリティはんぱなかったね今日ね、あれはとてもよかった。編曲がよかったのもあるけど、バランスも音色もばっちりすぎるほどによかった\n\u0026mdash; くろい (@kurokobo) March 27, 2015 パートリーダだけって、人数比的な意味でそもそも構成上の問題を抱えている状態なのだけれど、でもよかった、とても。\n信頼関係でできた音、すてきです。\nあと個人的にパート紹介のアルトファーストのあんぱんまん、編曲（元ネタあるのかもしれないけど）のセンスと楽器の使い方の『パート紹介兼楽器紹介』という目的の充足っぷりが好きでひじょうに評価たかかった\n\u0026mdash; くろい (@kurokobo) March 27, 2015 パート紹介の『紹介』としての本意は、合奏におけるパートと楽器の役割を構成面と音域面で切り取って強調してみせることなので、その意味で主旋律のアルトギターと超高域での修飾のソプラノギターという役割がはっきりした編曲だった。\nあとは情熱大陸の冒頭のソロね(`・∀・´ Ξ `・∀・´) よいうたいかたでした(`・∀・´ Ξ `・∀・´)\n\u0026mdash; くろい (@kurokobo) March 27, 2015 情熱大陸、あの編曲は十年以上前から存在してるし毎年どこかが弾いてるから散々聴いてて正直もう飽きてたんだけど、でも今日の冒頭のソロはその飽きをしばし吹っ飛ばしてくれるくらいよかった（まだ言ってる\n\u0026mdash; くろい (@kurokobo) March 27, 2015 これは書いたとおり。いままで聴いたなかでいちばん。ソロはこうでなくっちゃね。\nどの曲も市民館とは思えないくらい気持ちよく音が抜けてきたし、響かせれば響くのね。音の出し方がウマいんだろうなあ。\n市民館で開催する是非はいろいろ意見があるだろうけれど、お高くとまりがちなコンサートホールよりは確実にこっちのほうが客席との（心理的な）距離は近いし、聴く側も『日常の延長』の世界観で居られるので、親しみを感じやすいというのはあると思う。労せずとも “それなり” になってしまうコンサートホールと違って、こういう市民館でのコテコテの手作り感もだいすき。\nギターアンサンブル部といったって、別に音楽家の集まりではなくて、ただの部員の集まりだし、そうすると音楽はそれ自体が目的ではなくて、あくまで部活をたのしむための手段でしかないわけで。\n音楽側に寄せたらコンサートホールだろうし、部活側に寄せたら市民館だろうし、一長一短ではあるものの、好き放題たのしむ（部活らしい）自由度は市民館ならではだよなあと思う。\n来年は第 50 回。とうとう 100 まで折り返し地点。楽しみです。\n","date":"2015-04-05T12:00:51Z","image":"/archives/2092/img/DSC01381.jpg","permalink":"/archives/2092/","title":"多摩高校ギターアンサンブル部 第 49 回定期演奏会"},{"content":"“ははそのもり” と読むらしい。読めない。\n人づてにお手伝いを頼まれたので、舞台裏でちこちこ動き回ってきた。\n地元の中学校の吹奏楽部とか、地域の有志による合唱とかダンスとか、いろいろな演目のあるにぎやかなイベント。\n運営はたいへんそうだったけれど、ちびっこからお年寄りまでがいっしょの舞台で緊張しながらも楽しそうに歌ったり踊ったり吹いたりしているようすは、とてもよい。\nもう八回目だそうで、人手不足はこの手のイベントの常だけれど、普段発表する場のない団体にとっては貴重な場だし、こういうところでの共演をきっかけに予想外の展開につながることもままあるので、永く続くといいなあと思う。\n実働面でいえば、吹奏楽とギター合奏で、舞台準備のお作法にはローカルルールや細かな文化の違いもやっぱりあって、その辺はおもしろかった。だいたいは一緒だったので大きな違和感なく動けたけれど。\nぜんぜんしらないコミュニティにひとり紛れることになったけれど、平和な空気でよかった。いちどお客さん側として観てみたい。\n","date":"2015-03-27T12:00:18Z","image":"/archives/2080/img/DSC01375.jpg","permalink":"/archives/2080/","title":"第 8 回 柞の森音楽祭"},{"content":"3 月 20 日の金曜日、吉祥寺駅すぐの CHAIN GANG（吉祥寺チェインギャング）にて。\nこのウェブログでも何度か登場している渡辺良介大先生は、聴くだけで卒倒すると言われるほどにギターがウマいわけですよ。\nその彼が、大学卒業（というか就職）を機にギターから少しばかり離れてしまうとのことで、その区切りのためのライブがこれ。\nファンとして！！ 行かないわけが！！ ないじゃない！！\nさいこうでした。惚れる。\n茜色の街がたまンないよね、だいすきです。終演後のだらだらタイムでもういちど弾いてくれたので二回も聴けた。\n心地よい空間と心地よいひとたちと心地よい音楽でさいこうだった。さいきょうの組み合わせ。\nCHAIN GANG という場所、初めて行ったけれどオシャレなステキ空間だった。オープンマイクに小編成で突撃するのはアリかもしれないなあなどと。\nいつになるかわからないけれど、次のライブを楽しみに生きます。\n","date":"2015-03-26T12:00:47Z","image":"/archives/2074/img/DSC01366.jpg","permalink":"/archives/2074/","title":"渡辺良介 Farewell Live！"},{"content":"先日の 第 1 回 に続く Rubinetto 単独ライブシリーズの第 2 回を、3 月 14 日の土曜日、泉の森会館ホールで開催しました。\nキャパシティが前回の倍の 80 でしたが、ありがたいことにまたまた満員になって（ちょうすごい！！）、うれしいかぎりです。満員。満員て、前回と合わせて 120 ですよ、すごい。\nゼロから自分たちで作ってきて一年間でコレというのは、わりと本気で僥倖だと思っています。\nイイ感じにノリにノってきているので、来年度以降もエンジンふかしてばりばり進撃していきたいですね。そのうち 1,000 人くらいの団体になったらいいのです。\n泉の森会館、今回初めて使いましたが、おしゃれできれいで明るくてとてもよいところでした。アクセスもよいうえにお安いのです。\n音響面でいうと高音域の抜けがもうすこし欲しいところではありましたが、配置やらなにやらでの工夫のしようはありそうだったので、また使うときはもうちょいよくできそうです。また使いましょう。\n『演奏会』と『ライブ』という言葉、広義では同一視されますが、この二つがもつニュアンスとコンテンツ性の違いは、個人的にとても大事にしたいもののひとつです。\nごはんやら飲み物やらをコンテンツのひとつにできる場合は、プレイヤ側はもちろん、お客さんのスタンスも簡単にそれっぽくできますが、きれいな場所で客席がきちっと並んでいて音楽のみで勝負となると、どうしても『演奏会』っぽい空気になりがちですね。\n根本的に『演奏会』向けの所作振る舞いが身体に染み着いているということも否めないので、場数踏まないとです、経験値稼ぎはとても重要なのです。\n録画も録音もばっちりしたので、YouTube で順次公開していくつもりです。二公演分を組み合わせて、プログラム全曲分載せちゃう予定です。お楽しみに！！\nご来場いただいたみなさま、泉の森会館のスタッフのみなさま、ありがとうございました。\n自分たちで思う改善点もアンケートから読み取れる改善点もいろいろあるので、そのあたりを踏まえつつ、引き続きノリノリで活動していきたいですね。今後とも Rubinetto をよろしくお願いします。\n","date":"2015-03-25T12:00:37Z","image":"/archives/2032/img/IMGP6410.jpg","permalink":"/archives/2032/","title":"第 2 回 Rubinetto 単独ライブ"},{"content":"べらぼうにウマいアコースティックギタリストの友人ふたりが一夜限りのユニットを組んでライブに出るというので、会社帰りに行ってきた。\n原諒介と渡辺良介のふたりのリョウスケで、ユニット名は R2。\n3 月 12 日、木曜日、場所は宿河原のいつものポトスさん。\nこのときの動画を二曲だけ公開してくれたのでどうぞ。\nひとつめの “茜色の街” は右側の R のひとのオリジナル。彼のオリジナル曲の中ではぼくは一番これがすき。ふたつめの “風の詩” は押尾さん。\nこんなん誰が見てもカッコいいにきまってんだろ(ﾉｼ・ω・)ﾉｼ\nこの日は、それぞれのソロパフォーマンスを挟みながら、お互いのオリジナル曲をデュオアレンジにして弾きあうプログラム。行ってよかった。\nしかしやはりこの二人が組むとかもう卑怯以外の何物でもない。\nあの二人が組んだら鬼に金棒にもほどがあるだろ…… と思って聴きに行ったけど、金棒持ってる鬼が二人居る状態だったしもうたいへんでした\n\u0026mdash; くろい (@kurokobo) March 12, 2015 ふたりとも就職で散り散りになってしまうのが惜しい。\n今回は生で聴けてよかったけど、このままだと世界が損をするので、ふたりともばんばん YouTube とか Facebook とか Twitter とか何でもいいから自分の演奏をインタネットに載せてください！！\n","date":"2015-03-23T12:00:41Z","image":"/archives/2029/img/DSC01326.jpg","permalink":"/archives/2029/","title":"Shukugawa Live！"},{"content":"FINAL FANTASY の曲をシエナウィンドオーケストラさんが演奏するコンサート、BRA★BRA FINAL FANTASY Brass de Bravo with Siena Wind Orchestra に行ってきた。3 月 7 日の土曜日、東京オペラシティコンサートホール、タケミツメモリアル。\nこの会場、高校生のころに贅沢にも演奏者側として使ったことがあって、ぼくが初めて『よいホールで弾くとたのしい』ということ明確に意識したのがそのときだった。だから思い入れのある場所。\nそんな場所で FF でしかもシエナさんとか、これがテンションあがらずに居られるか状態なわけで。\nシエナさんを生で聴くのは初めてで、顔ぶれからするとどうも全員が一軍というわけではなさそう（？）な気がしたけど、それでだってやっぱり鬼のようにかっこうよかった。ちょうウマい。\n大勢で演奏するときの迫力ももちろんなのだけれど、ベテラン勢（たぶん）の小編成でのジャジィなアレンジとか金管アンサンブルアレンジとか、そういうのがさいこうにきもちがよい。耳が溶ける。\nゲーム音楽っていう世界自体のカジュアルさと、吹奏楽っていうジャンル自体のカジュアルさってすごく相性がよさそう。オーケストラにある根底的な “おカタさ” がないので、身構える必要がまったくない。最初から楽しむ気満々で居られるあたり、ストレスフリーですばらしい。\n吹奏楽アレンジでの公演は公式には今回が初らしいのだけれど、ぜひ今後も継続して続けていただきたい感じ。\n先々月の Distant Worlds、先月の THE LEGEND of RPG に続くぼくのゲーム音楽月間（？）、これで完結。\nでもまたいきたいね、ゲーム音楽はおいしい。\n","date":"2015-03-22T12:00:15Z","image":"/archives/2023/img/DSC01374.jpg","permalink":"/archives/2023/","title":"BRA★BRA FINAL FANTASY Brass de Bravo with Siena Wind Orchestra"},{"content":"行ってきた。3 月 1 日、日曜日、大田区民センター。\n品川ギターアンサンブルさんを生で聴くのは初めて。この日の共演は目黒クラシックギタークラブさんと、毎度おなじみアンサンブル・ジターノさん。\n品川さんの演奏、とてもきっちりしていた。\n演奏、とてもかっちりしてる\n\u0026mdash; くろい (@kurokobo) March 1, 2015 楽譜にとても真摯に向き合って、そしてとても丁寧に組み立てていっている印象。だからリアルタイムに音を作るというよりは、そういう練習で事前に作り上げておいた音楽を、練習のとおりに当日の舞台上で再現する、みたいなアプローチをしているように見えた。\n共演のジターノさんはいつも通り相変わらず自由な感じ。リアルタイム指向（？）だし品川さんとはすこし特性が違うと思っているので、異文化交流みたいな印象でおもしろかった。\n当日の動画が全曲公開されたようなので載せる。多いので 再生リストでリンク。埋め込みはダメっぽいので、YouTube に飛んで から観てね。\nhttps://www.youtube.com/playlist?list=PLjFGBkRcU0_ojXnQd469seW6CLcOG6M6u\n昭和感に満ちた平和で落ち着いた時間。のんびりしていてよい。\n知らない団体の演奏会にはこれからもいろいろ行きたいし、弾く側目線で言えば共演もしてみたい。他のアンサンブル団体のひとと一緒に舞台に乗るの、高校生の頃の JGA フェスティバルギターアンサンブル以来なにもない気がする。あったっけ。\nそんなわけで、Rubinetto は共演依頼も受付中だよ！\n","date":"2015-03-21T12:00:49Z","image":"/archives/2018/img/DSC01284.jpg","permalink":"/archives/2018/","title":"第 16 回 品川ギターアンサンブル演奏会"},{"content":"Rubinetto の結成から一年。5 人で始めて、いまは 14 人。\nいろいろな方々と共演する形でこれまで都合 4 回のライブに出演してきましたが、ついに Rubinetto の名義で単独でライブを開催するに至りました。\n“いつもの場所” とも言えるおなじみのカフェレストラン、ポトスさんにて。2 月 15 日、土曜日。\nお客さんもたくさん来てくれて満席になった（すごい！）し、終演後のわちゃわちゃもおもしろかったし、何より演奏をみんなでによによしながら楽しめたので、あんまり言うこともないのですが。\nライブの様子をさっそく二曲だけ公開したので紹介します。\nまずはこちら、葉加瀬太郎さんの Girls Talk。かわいい曲です。\nカホンは最近加わったのですが、ウクレレベースのふくよかかつ濃厚な低音との相乗効果で、ビート感が強烈にふくらんで推進力が爆発的に増強されます。さいこうです。\nそしてこちら、これも葉加瀬太郎さんの Born to Smile。これまで MV 版 や カフェコンサート版 も公開してきましたが、人数と楽器の増強で大幅にパワーアップしました。\n今回来られなかったみなさま、3 月の 14 日の土曜日に今回とおなじような演目で『第 2 回 Rubinetto 単独ライブ』を開催しますので、ぜひどうぞ。\n14 時開演っていう昼間だし、登戸駅からたったの二駅の狛江駅すぐなんていうステキなロケーションなので、部活帰りの学生さんにもちょうどよい具合です。ホワイトデーですし、デート会場にでもしていただければ。\nそしてそして、単独ライブ以外にも、三月末のカフェコンサート と、五月のコンサート、さらには 八月のコンサート への出演がすでに決まりつつあります。\nあいかわらず、団員は募集中です。\n今月すでにひとり加入してくれていますし、なんていうか、みんなで気軽に音楽を楽しめる場をひろく解放していきたいというのがぼくのなかにあるので、技量とかどうでもよくて、だからぼくらの演奏を見て聴いてみて、楽しそうだなー混ざりたいなーって思ってくれたらほんとうにまず見学に来てみちゃえばよいと思っています。\nお気軽にという言葉以上にお気軽なくらいでじゅうぶんなので、お気軽にどうぞ。\n来てくれたみなさま、ポトスのみなさま、ありがとうございました。ギター好きのみなさま、3 月 14 日に泉の森会館で会いましょう 🙂\n","date":"2015-02-23T03:30:51Z","image":"/archives/2002/img/IMGP6083.jpg","permalink":"/archives/2002/","title":"第 1 回 Rubinetto 単独ライブ！"},{"content":"JAGMO という、ゲーム音楽を専門に扱うプロオーケストラの、『THE LEGEND of RPG 伝説の交響楽団』と題されたコンサートに行ってきた。2 月 8 日、五反田ゆうぽうとホール。\n先日の反省 があったので今回は悪い意味で “身構えて” いったのだけれど、よい意味でおおいに裏切ってきてくれて、心配する必要はまるでないくらいに、しっかりと音楽を聴けた。きちんとゲーム音楽で、きちんとレベルの高いオーケストラだった。\n曲はこちらのタイトルから。単品やらメドレーやらでもりだくさん。\nポケットモンスター クロノ・クロス クロノ・トリガー キングダム・ハーツ ロマンシング サ・ガ ファイナルファンタジー ハコもちょうどいいし、映像もなく演出も控えめで、ストレートに音──もちろん生音──で勝負してくる感じ。ゲーム音楽の “音楽としての” よさを、オーケストラを遣って最大限に表現してくる感じ。いいですね。\nオーケストラの響きってやっぱりすきだなあと。そしてコテコテのクラシック音楽もいいけれど、ゲーム音楽というよい意味で気楽な世界もやっぱりすきだなあと。\nいいトコどりってかんじで。ぜいたくだった。これはまたいきたい。\n","date":"2015-02-22T14:50:31Z","image":"/archives/1992/img/DSC01111.jpg","permalink":"/archives/1992/","title":"JAGMO の THE LEGEND of RPG に行ってきた"},{"content":"1 月 22 日、木曜日。会社を午後おやすみにして、東京国際フォーラムのホール A で開催された Distant Worlds： music from FINAL FANTASY THE JOURNEY OF 100 に行ってきた。\n“music from FINAL FANTASY” と題されている通り、ファイナルファンタジーシリーズの音楽のコンサート。\n幼少期はあまり TV ゲームでは遊ばなかったおかげで、ふぁみこん時代の FF は触ったことがない。そのうえ VII から IX しか経験がないという、言ってしまえばニワカ的なアレだけれど、それでも FF は好きなゲームのひとつ。とくに VII。\nそんなわけで、東京フィルハーモニー交響楽団さんの生演奏を巨大スクリーンによる映像上映とともに楽しめるらしいこのコンサートは、予約してからというものほんとうに楽しみだった。もちろん S 席。\n……で、個人的には、“音楽” を聴きに行ったつもりだったのだけれど……。\nでも、蓋を開けてみれば “超贅沢なプロモーションビデオ観賞会” という感じだった。\n例えていうなら “同窓会” に近い。会場がレストランだからって “料理” を目的に同窓会に参加してもイマイチなのと同じで、この手のイベントも、コンサートだからって “音楽” を第一目的にするべきではないのかもしれない。モチベーションは “回顧” に見るほうがよいのかなと。\n楽しかったは楽しかったけれど、でもちょっと残念。\nというのも、とくに PA が微妙だったから。“生演奏” と “生音” は、やっぱりぜんぜん違うのよね。\n聴こえなくていい雑音、具体的には譜めくりの音や衣装の擦れる音までばっちり拡声されてしまっていたし、あとはコーラスのバランスも悪かったし……。コーラスはそもそもだいぶズレていたので、周りの音が聴きづらくて歌いにくかったんだろうなあって気がしている。音の細さと狭さも気になった。\nいや、技術的にはアレだけのハコでアレだけの音がつくれればちょーすごいとは思うのだけれど…… でも根本的に東京国際フォーラムなんていう “コンベンションセンタ” で合唱付きのオーケストラって、そもそもの会場の選択にちょっと無理があるよね、って思わざるを得ない。そんな具合。\n無茶を承知で言えば、仮に一万人分の集客力がある場合、『キャパシティ 5,000 のコンベンションセンタで PA アリの公演を二回』よりは『キャパシティ 2,000 の “コンサートホール” で PA ナシの公演を五回』のほうが理想ではある。“音楽” をウリにするならなおさら。\nもちろん、巨大スクリーンのあるコンサートホールなんてすごく少ないだろうし、一回でたくさんひとを呼べた方がストレートに言えば利益率は上がるだろうから、商業的には間違っていないとは思うのだけれど。当然、楽団さんはじめ関係者のスケジュールもあるわけだし。\nそれでもやっぱり S 席で 9,000 円という海外のまっとうな楽団の来日公演並みの値段設定であるなら、それならそれでそれに見合うだけの音楽は聴かせてもらいたかった。\nあの人たち、ほんとうはもっとうまいはず。もったいない。惜しい。よいホールで生音で聴きたかった。\nPA 以外でも、一曲めのプレリュードの冒頭、ハープとコーラスが始まったときは、正直このクオリティで最後まで行ったらどうしようと言葉通り悪い意味でたいそう不安にもなったけれど……。\nとはいえゲーム音楽って、ゲーム音楽単体としての価値もさることながら、それにまつわる個々人の記憶や思い出があるからこそのコンテンツでもある。だから “コンサート” ではなく『音と映像で昔を懐かしむ』ためのイベントだと割り切ってしまえば、書いたとおり『超贅沢なプロモーションビデオ観賞会』としてとても楽しいイベントだったのは事実。\nLiberi Fatali とかバトルメドレーとか、片翼の天使とか。アンコールのアレも。音の数が増えると映像効果と合わさって迫力が出てくる（ごまかしが効きやすいってことでもあるんだけどね）し、ボーカルのある曲はボーカルさんのしっとりとしたうたに映像の空気がよく馴染んでいたし、このあたりはプロモーションとして、あるいは映像作品として秀逸な出来だなあと。\nああ、もう一度あそびたいなあと、もういちどサウンドトラックを聴きたいなあと、あそんでいた当時のわくわく感や興奮がふんわりと浮かんできて、そう思った。満足。\nFF VII、たまにふとやりたくなってひっぱりだすんだけど、早いと爆破ミッションが終わるか、長くても神羅ビルについたくらいで満足してしまって、エンディングまでは結局数回しか行ったことがないのよね……。\n2 月の “JAGMO -伝説の交響楽団- THE LEGEND OF RPG COLLECTION” も行くし、 3 月の “BRA★BRA FINAL FANTASY Brass de Bravo with Siena Wind Orchestra” も行く。これも楽しみ。\nゲーム音楽、よいものだ。\n","date":"2015-01-24T17:40:05Z","image":"/archives/1884/img/DSC01049.jpg","permalink":"/archives/1884/","title":"FF をオーケストラで聴く。Distant Worlds に行ってきた"},{"content":"背景 ギター合奏界隈で集まってお互いに自己紹介するときに、『○○高校の△期の□□です』という言い方をよくします。ぼくでいえば、『多摩高校の 48 期の黒井です』といった具合ですね。\nこれ、同じ学校であればすぐにどの世代か理解できて自分との関係性も導き出せるのですが、当然ながら他校の数字で言われてもよくわかりません。それどころか、学校によっては、“期” ではなく定期演奏会の “回” で数えているところもあります。\nもちろん、他校出身の友人がいるのであれば、そのひとを基準に計算することは容易いし、慣れてくれば自分の頭の中に対応表が出来上がってくるものです。最近では『多摩高校換算で××期相当』と自己紹介に最初から含めてくる強者もよく目にするようになりました。\nとはいえ、そういうコミュニティの空気に慣れるまでは、それはなかなかできるものではありません。\n相手の世代がわかることで、初対面でも、コンクールで弾いていた曲、交流会であったできごと、共通の先輩や後輩など、話題の幅がかんたんに広がります。とくに相手が自分に近い代である場合はなおさらで、思い出話に花を咲かせやすくなるというものです。\nそんなわけで、初対面でも話のきっかけを増やせるかもしれない仕組みのひとつとして、これを作りました。\n……もともとこういう変換機構は欲しいなあとは思ってはいたのですが、実装に着手したいちばん直接的な理由は、最近 老害おじさん 友人がこんなことをやっていたからです。\nこれでどうだ pic.twitter.com/jASiSVsWPi\n\u0026mdash; めがたか🦅🌳megataka (@megataka) January 12, 2015 作ったもの これです。\n多摩高期数変換機 これは、中学や高校でギター合奏を行う部活動や同好会に所属している、または所属していた方々を対象にした、\n『自分の学校名と期数』か『自分の学校名と引退時の定期演奏会の回数』か『自分の誕生年度』を入力すると 自分がほかの学校では何期生に相当するのか、第何回の定期演奏会の代かがわかる という機能をもつ Web サービスです。\nつかいかた スマートフォン世代の方々にはすぐ馴染めるようなインタフェイスにしたつもりです。\n最上段のタブで変換方法を選びます [期から] は、[学校] と [期] を元に計算します [定演から] は、[学校] と [引退時の定演] を元に計算します [誕生年度から] は、[誕生年度] を元に計算に変換します 変換方法に応じた入力欄が表示されるので、すべての欄で適切な値を選択します 入力が完了すると、画面下部に結果が表示されます お好みで [結果をツイートする] ボタンでツイートします また、右上の [設定] ボタンからは、いくつか挙動を任意で変更できます。設定は任意のタイミングで変更可能で、変更は即座に結果に反映されます。\n[定演の回を表示する] のチェックを入れると、計算結果に期数だけでなく、その期の代が引退したときの定期演奏会の回数も併せて表示されます。デフォルトは無効です。なお、定期演奏会の回数はツイートには含まれません。\n[マイナス表示を許可する] のチェックを入れると、“1 期” のひとつ上を “0 期”、もうひとつ上を “-1 期”…… とさかのぼって表示するようになります。デフォルトは無効で、“1 期” より上の代はすべて “-” と表示します。“1 期” のひとつ上を “0 期” にするべきか “-1 期” にするべきかは議論の余地がありますが、実装がラクなのでひとまず “0 期” にしています。\n考えたことというか工夫というか 世代の区別の根拠は学校によって “期” だったり “回” だったりといろいろなので、そういった差を吸収するため、複数の変換方法を用意しました。\nまた、処理を JavaScript で完結させているので、サーバとの余計な通信は発生しません。\n拡張しやすくするため、学校の情報はすべてひとつの連想配列にまとめました。結果の表示枠もすべてその配列から動的に生成させているので、配列に一行足すだけで学校が追加できます。\nあとは結果が “-” になる学校の分はツイートに含めないとか、入力時に学校に応じて期の選択肢が変わるとか、細かなところは手を入れています。\n未実装、あるいは検討の余地 あくまで機械的に算出しているため、『定期演奏会が必ず一年に一度だけ開催される』という暗黙の前提を置いています。この前提に従っていない歴史がある場合は、計算結果が狂います。\nおくさまに見せたら、『Facebook か Twitter と紐付けて結果をデータベースに貯めて、誰と同期だとか誰が後輩で先輩だとかわかるようにしたら？』と言われました。さすが IT やさんです。\nまとめ 本業のおしごとでは IT 系と言いつつ Web アプリケーション系のコードを書く機会がまるでないので、久々に趣味のプログラミングって感じでおもしろかったです。\njQuery Mobile、便利ですね。まともに使ったのは恥ずかしながら初めてだったんですが、好きになりました。\nこういうのって、コードを GitHub に載せるとイイんでしょうか。最近のお作法ってどうなんだろう。\nちなみにこの勢いに乗って、実は Python + jQuery でそれなりに機能する CGI を作ったんですが、それはまた別のお話。一般公開はしていないです。\n","date":"2015-01-20T16:26:54Z","image":"/archives/1859/img/DSC08475.jpg","permalink":"/archives/1859/","title":"自分が他校の何期生相当かすぐわかる、ギター合奏クラスタ用 Web サービス『多摩高期数変換機』をつくった"},{"content":"昨年の 12 月 28 日、日曜日、アルトギターを背負って足立区のギャラクシティへ。アンサンブルジターノさんの練習に混ざってきました。\n先日、新日本ギターアンサンブルのギタークリスマスコンサート で、ジターノさんの中のひとが何人か来てくれていて、その時にお声かけいただいたのがきっかけです。こういうの、うれしいですよね。\nアンサンブルジターノ ジターノさんは、所沢高校と大宮高校のギター部の OB さんを中心に 2010 年の 10 月に結成された社会人ギターアンサンブル団体です。\nぼくがジターノさんの演奏会を初めて聴きに行ったのが二年ちょっと前、2012 年の秋でした。当時はたしか 10 人も居なかったと記憶していますが、今ではもう 15 人くらいに増えています。\nその 2012 年の秋、初めて聴きに行った日の、演奏会のあとのぼくの Twitter がこれです。\nおわた(*´ω｀) ちょう平和な空気でものすごくのんびり聴いてしまった(*´ω｀)\n\u0026mdash; くろい (@kurokobo) September 30, 2012 規模が大きいわけではないけど、でもこういうギター欲ともいうべきもので集まった方々の演奏は大概たのしい。やりたくてやってる空気ってうまくいえないけどそれだけで魅力的よね(・∀・) どう理屈をこねくりまわしたところでやっぱりこれが原点だよなあと(・∀・) 次が楽しみだ(・∀・)\n\u0026mdash; くろい (@kurokobo) September 30, 2012 この感覚は今でも健在で、だから今回練習に見学に行って思ったのも、“やりたくてやっている” というモチベーションの強さでした。\n中学高校の部活でもなく大学のサークルでもない団体で、社会人になって “やりたいから” “すきだから” をモチベーションにして活動を続ける、というのは実はかなり体力が要るものです。\nその意味で、もう四年以上も続いているジターノさんの活動っぷりは、ぼくにとってはとても魅力的だし好きなものだし、参考にしたいものだし、そして理想的だと思っています。\n練習内容はネタバレになってしまうのであまり詳しくは書けませんが、和気藹藹とした空気、根本的な “仲のよさ” はこのひとたちの明確な強みだなあと、そんなことを思いながら、みなさんに混ざってアルトギターを弾いていました。\nぼくが弾いたことがある曲もこの日の練習内容に入っていて、予想はしていたものの同じ曲でも解釈や表現が違うのはおもしろかったです。その “違う表現” に至るまでの練習の方法論も当然違ってくるわけで、これは違う団体の練習に混ざらない限り味わえない新鮮さでした。椅子の配置などの文化もけっこう違いがあるようですね。\nそういえば、某音楽院が編曲して発行している楽譜に弦楽用の原譜にあるアーティキュレーションや強弱記号がぜんぜん反映されていないのは少し気になりました（もちろんジターノさんの責任ではないですが……）。\n練習のあとは忘年会になった ようで、残念ながら別の集まりがあったため参加はかないませんでしたが、次は混ざっていきたいですね。ちらっちらっ。\nそんなわけで、他団体の文化や空気に触れられたよい機会でした。こういう他団体の練習の見学の機会、増やしたいなあとひそかに思っています。ちらっちらっ。\n今年の 9 月 23 日（水・祝）に定期演奏会を開催 するそうなので、興味のある方は是非。ぼくも聴きに行く予定です。\nジターノのみなさま、ありがとうございました。またよろしくお願いします。\n","date":"2015-01-20T16:00:34Z","image":"/archives/1841/img/DSC00644.jpg","permalink":"/archives/1841/","title":"アンサンブルジターノの練習に混ざってきた話"},{"content":"昨年もまとめましたが、今年もまとめます。\n今年は『イベントごとにエントリをひとつ』という記録の付け方をひそかに目標にしていました。\nというわけで、だばだばと列挙していきます。\n新堀芸術学院の春期定期公演で、ギター合奏と PA について考えた（2014 年 2 月 26 日） 全国職場バンドフェスティバルで、さいこうにイケてる吹奏楽を聴いてきた（2014 年 3 月 3 日） いちむじんの結成十周年記念コンサートに行ってきた（2014 年 3 月 10 日） 引地台中学校クラシックギター部の定期演奏会に行ってきた（2014 年 3 月 29 日） 北杜高校ギター部のスプリングコンサートで、日本でいちばんの合奏を聴いてきた（2014 年 4 月 1 日） 多摩高校ギターアンサンブル部の定期演奏会で、自分の原点に触れてきた（2014 年 4 月 8 日） ジョアン・リラさんのライブとワークショップで、ホンモノのボサノヴァに触れてきた（2014 年 4 月 13 日） 相模原中等教育学校クラシックギター部の定期演奏会で、とんでもなく奇跡的な演奏を聴いてきた（2014 年 4 月 23 日） 須川展也さんのデビュー 30 周年記念コンサートで、変幻自在のサクソフォンを聴いてきた（2014 年 4 月 29 日） ブリトニー・スピアーズさんの定期公演、Peace of Me に行ってきた（2014 年 5 月 19 日） イマジン・ドラゴンズのライブで、ドラムサウンドに圧倒されてきた（2014 年 5 月 25 日） ペペ・ロメロさんのコンサートで、伝説の銘器トーレスの音を聴いてきた（2014 年 6 月 1 日） DANROK のポーランド公演記念コンサートで、突っ走るギターを聴いてきた（2014 年 6 月 23 日） ソニー吹奏楽団の定期演奏会で、緻密で丁寧な吹奏楽を聴いてきた（2014 年 6 月 24 日） 大宮高校ギター部の定期演奏会で、なつかしい堅実な音を聴いてきた（2014 年 6 月 27 日） ここまでで半年。クラシックギターを中心に、ロック、吹奏楽、ボサノヴァなどに触れた日々でした。\n続いて後半です。\n虹晴れギターアンサンブルコンサートに行ってきた（2014 年 7 月 12 日） 宿河原のいつものお店、ポトスのオープンマイクに参加してきた（2014 年 7 月 13 日） 吹奏楽祭 2014 マーチ＆ポップス・イン・HIBIYA に行ってきた（2014 年 7 月 14 日） NHK 交響楽団の N 響ほっとコンサートに行ってきた（2014 年 8 月 6 日） ギターサマーコンサート 2014 に行ってきた（2014 年 8 月 19 日） 某社の某イベントでビッグバンドと吹奏楽を聴いてきた（2014 年 8 月 25 日） 全国学校ギター合奏コンクール 2014 で、ギター合奏のことを考えた（2014 年 8 月 27 日） 坂戸高校の文化祭で、ギター部の演奏を聴いてきた（2014 年 9 月 3 日） 多摩高校の文化祭で、ギターアンサンブル部の演奏を聴いてきた（2014 年 9 月 11 日） 相模原中等教育学校の文化祭で、クラシックギター部の演奏を聴いてきた（2014 年 9 月 25 日） 宿河原のいつものお店、ポトスのライブに参加してきた（2014 年 9 月 30 日） 岡上分館のカフェコンサートに出たよ（動画もあるよ！）（2014 年 10 月 16 日） 宮川彬良のせたがや音楽研究所 #2 に行ってきた（2014 年 10 月 23 日） アルヴァロ・ピエッリさんのギターリサイタルに行ってきた（2014 年 10 月 25 日） ポトスでのライブと、Rubinetto 近況（2014 年 11 月 30 日） ソニー吹奏楽団のファミリーコンサートに行ってきた（2014 年 12 月 2 日） 洗足学園音楽大学 FUYUON! 2014 クラシックギターコース演奏会に行ってきた（2014 年 12 月 3 日） 神奈川県高等学校総合文化祭の器楽管弦楽演奏会に行ってきた（2014 年 12 月 14 日） ギタークリスマスコンサート 2014（2014 年 12 月 29 日） あいかわらず中心はクラシックギターでした。人生の中で諸事情により吹奏楽成分が増えてきていますが、ほかにもオーケストラ、ビッグバンドなどなど、いろいろです。\nRubinetto が本格的に演奏活動を始めたのも、7 月以降でしたね。ぼくのギターの中心はどうしたって新日本ギターアンサンブルなので、自分の “看板” というかアイデンティティは主にそれではありますが、Rubinetto は Rubinetto で息の長い団体にしたい、という思いが強くあります。\nいろいろな音を聴く、ということは、自分の音を磨くうえで絶対に必要なことだと思っています。\n身近な目標、自分にとっての “理想の音” を探すこと。おおいに妄想して、脳内で “理想の音” が鳴らせるようになること。さらにその脳内の “理想の音” を磨きあげていくこと。いろいろな音を聴いて、いろいろな弾き方をみて、きれい、きたない、かっこいい、かっこわるい、そういう自分の中に湧く素直な感想をだいじにして、自分にとっての “理想の音” を高めていくこと。\nここ何年も、ずっとそういうことを続けています。\n意識しよう、うたおう、よくいわれるこういう指摘は、“脳内で理想状態を思い浮かべて” と、“その理想状態をなぞるように弾く” という、ただそれだけの、ほんとうにただそれだけのことなのです。だからこそ、“理想状態” そのものを高めることが、成果に直結するわけです。\n脳は万能なのですヽ(・∀・)ゝ 妄想の世界ならどんな弾き方だってできるしどんな音だって鳴らせるのですヽ(・∀・)ゝ 考えるだけで本当に鳥肌が立つような、そういう理想的で究極に至高で最高な音を、脳内で演奏できるようになろうヽ(・∀・)ゝ 出し方はあとまわしでいいのですヽ(・∀・)ゝ\n\u0026mdash; くろい (@kurokobo) August 5, 2013 人間のボトルネックは身体動作を伴うアウトプット部分で、決して脳ではないのですヽ(・∀・)ゝ 脳の万能な力を信じて、枷をはめずに、脳に自由にさせてみるのですヽ(・∀・)ゝ 言語化しようとせずにヽ(・∀・)ゝ 言語化すると言語化できる範囲の外に出られなくなりますヽ(・∀・)ゝ\n\u0026mdash; くろい (@kurokobo) August 5, 2013 脳内で至高の音を出す自分の姿が想像できたら、それを現実世界でトレスしますヽ(・∀・)ゝ 音を出した瞬間、きっと理想とのギャップに幻滅するヽ(・∀・)ゝ そこからが練習だヽ(・∀・)ゝ りそうにひたりげんじつをみつめもだえくるしんであがくのですヽ(・∀・)ゝ\n\u0026mdash; くろい (@kurokobo) August 5, 2013 宗教じみてきたけどわりとそういう感覚、ぼくは大事にしてる。想像できないものは現実にできない\n\u0026mdash; くろい (@kurokobo) August 5, 2013 『頭を使って演奏する』ことは、脳にたくさん想像と妄想をさせながら弾くことに似ている。想像する作業と指を動かす作業は半分くらい連動してて半分くらいは別人格。でもどっちもぼくの世界\n\u0026mdash; くろい (@kurokobo) August 5, 2013 理想状態を明確に想像できることはやっぱりだいじだ\n\u0026mdash; くろい (@kurokobo) August 2, 2014 顔文字がウザい（書きながら自分でそう思った）けれど、以上、一年半くらい前のツイートをお送りしました。\nギター合奏やってる人は一定以上のレベルの演奏聞く機会が他ジャンルに比べて極めて少ないよなー。理想のサウンドが脳内に無いってなんというか不幸。\n\u0026mdash; めき𓆏 (@kaerukumaen) December 18, 2014 大学では合唱サークルにいたけど、好きな合唱団ないし、アンサンブルは？\nって聞かれたら、みんな何かしら答えられたよね。ルネサンスものならどこどこ、黒人霊歌はどこどこ。どこどこは此処は素晴らしいけど、ここがあんまり好きになれない等々。吹奏楽部の人なんかも答えられるのじゃないかしら。\n\u0026mdash; めき𓆏 (@kaerukumaen) December 18, 2014 部活で初めて、10年、20年と合奏用ギター弾いてる人に、好きなチェンバロギター奏者は？とか聞いても、一割も答えられないんじゃなかろうか。\nいや、仕方ない部分もあるけど、\nそれで続けていくのは俺だったらきついなー\n\u0026mdash; めき𓆏 (@kaerukumaen) December 18, 2014 嘘だろ俺の弾いてる楽器こんな音出せるのかよ…、ってなったり、どうやったらこんな演奏出来るんだろうってもう動きから顔から全部真似てみたりした経験なしに熱を冷まさずにいられるもんなのかな。\n\u0026mdash; めき𓆏 (@kaerukumaen) December 18, 2014 元々市場が小さい上にｸﾞﾙｰﾌﾟ間交流が絶望的に少ないから、母数が小さいんである意味必然なんですよね RT @kaerukumaen: ギター合奏やってる人は一定以上のレベルの演奏聞く機会が他ジャンルに比べて極めて少ないよなー。理想のサウンドが脳内に無いってなんというか不幸。\n\u0026mdash; Jun Matsumoto (@Jun_mats) December 19, 2014 基礎練習、個人練習、ｱﾝｻﾝﾌﾞﾙ練習それぞれと同じくらい聴きにいくことに力を割いてもよいと思うよ\n\u0026mdash; Jun Matsumoto (@Jun_mats) December 19, 2014 偉大なる先達のコトバを引用して終わりにします。\nそんなわけで、全部で 35 回の演奏会に聴いたり観たり出たり手伝ったりした一年でした。前年比で 10 回増しです。\n来年…… は、動きが鈍りそうな気はしますが、引き続きいろいろなところに顔を出していきたいところです。\n","date":"2014-12-29T11:55:30Z","image":"/archives/1831/img/DSC08257.jpg","permalink":"/archives/1831/","title":"ぼくが 2014 年に聴いたり観たり出たり手伝ったりした演奏会まとめ"},{"content":"新日本ギターアンサンブルの毎年恒例のイベント、ギタークリスマスコンサート。今年は 12 月 20 日の和光大学ポプリホール鶴川での回と、12 月 23 日の白寿ホールでの回の、全二回。\nたのしかった。\n一年前、去年のこのコンサートのエントリ ではこんな大げさなことを書いたけれど、\nようやく、ギター人生で初めて “納得のいく” 演奏会になったなあと\nぼくはこう弾けばいいのかと、こういう音をつくればよいのかと、これがぼくのスタイルなのかと、そういう納得感\n今年は上で言う “ぼくのスタイル” をじっくりことこと煮込んだ一年だった、気がする。\n去年はステージ上で突然舞い降りた爆発的なインスピレーション（？）のおかげで、流れの中を興奮して泳いでいたらいつの間にか終わっていた、みたいな感じもあったけれど。今年は自分の特性と自分の音を一年かけて咀嚼して煮込めて吸収できたので、去年と比較するとだいぶ落ち着いて冷静に臨めていた感があり。\n反省すべきところもいろいろあったけれど、反省するということは別にしゅんとしてごめんなさいして縮こまっていることではなくて。過去を省みて問題とその対策を考えて、未来の自分の行動にがんがん活かしていくこと、ただそれだけ。\nそんなわけで、反省点もあるし、目標もあるし、めざすべきひとも居るし、じわじわと登っていけている実感もあるし、相変わらず脳内には完璧な自分が居るし、解決したい課題もある。\nよい状態、だと思うわけで。来年の演奏会めがけて、地道に進んでいこうと思う。\n","date":"2014-12-29T09:01:20Z","image":"/archives/1815/img/DSC00569.jpg","permalink":"/archives/1815/","title":"ギタークリスマスコンサート 2014"},{"content":"平成 26 年度神奈川県高等学校総合文化祭の中の、第 14 回神奈川県高等学校器楽・管弦楽演奏会に行ってきた。長い上に何をどう略しても “高文連” にはならないのがミソ。“高文連” は主催者と主管者、神奈川県高等学校文化連盟。\n12 月 14 日、日曜日。去年までと変わって会場は昭和音楽大学のテアトロ・ジーリオ・ショウワ。\n相模原中等教育学校クラシックギター部さんの出演時間がちょうどぼくの隙間の時間だったから、ちょうど新百合ヶ丘に居たことだし、覗いてきた。午前の多摩高校ギターアンサンブル部も聴きたかったのだけれど、時間が叶わず。\nこのホール、本来の用途、つまり “劇場” として使えばすばらしい場所ではあるものの、残念ながらギター合奏とは特性上いまいち相性がよろしくない。実は全日本学生ギターコンクール（当時はまだこの名称だった）が 2007 年にいちどだけここを会場に開催されたのだけれど、それ以来そこでは開催されていないあたりからも、いろいろとうかがえるような、そうでもないような。\nというわけで、実際の演奏も、すごく弾きづらそうだなあというのが第一印象。N 人の合奏というより、N 人の独奏の集まりに聴こえてしまって、この部のポテンシャルは絶対こんなものではないはずなのだけれどと思いながら聴いていた。\nもともと人数が多い部活がお互いの音を聴きにくい環境で人数を絞って演奏すると、それまで人数の多さでどうにかなっていたところが丸裸にされるので、そういう意味ではこのメンバでの本領発揮はもうちょっと先なのかなと。とくにトリプティークはぼくのお気に入りの曲なので、定期演奏会ではイケイケなのが聴けることを期待。伸び代はたくさん。\n中等さんの定期演奏会、毎年経済産業省のアレとかぶるのがつらい……。\n","date":"2014-12-14T14:20:03Z","image":"/archives/1809/img/DSC00490.jpg","permalink":"/archives/1809/","title":"神奈川県高等学校総合文化祭の器楽管弦楽演奏会に行ってきた"},{"content":"11 月 30 日、日曜日。会場は同大学シルバーマウンテンの二階。\n夏の演奏会（NATSUON）には結局行けなかったので、去年の FUYUON 以来一年ぶり。\nお目当ては何と言っても世界初演である竹内先生の Surge VI。あとウクレレ。\nSurge VI、来年のコンクールの課題曲でもあるので多くのことは書かないけれど、とにかく、ぼくは好きだった。\n今までの Surge シリーズだと、ぼくは IV のあのたまらなく切ないメロディがほんとうに大好きで、初見で弾いた瞬間にのめりこんだ記憶がある。VI を聴いたときの感覚も、あの時のそれに近かった。とてもよい。\n今回は五人編成だったから、これがコンクールで八十人規模での合奏になったときにどうなるか、楽しみで仕方がない。いつか自分が弾きたい。Surge シリーズが完成したら、Surge 全曲を続けて弾くコンサートを開きたい。\nウクレレは相変わらずテクニックがぶっ飛んでいてさいこうだった。\nでも生音よりアンプを使った方がよかったのでは、とも。ウクレレの音色と響きを聴かせる部分ももちろんあるものの、エンタテインメントとしての性格が強かったわけだし、ちょっと惜しい。\n演奏者の彼はこれで卒業してしまうそうなので、学校関係ない場で演奏しまくってくれることに期待したい。聴きに行こう。\n最後に演奏したブエノスアイレスの冬は、独奏者である演奏者のポテンシャルを引き出すという意味ではこのくらいテクニカルな編曲のほうが似合うなあって感じで、しっかりとハマった仕上がりでかっこうよかった。\nこの曲も、いつか自分で弾いてみたい曲のひとつ。\n去年より人数が減って五人になって、はてさて来年はどうなるのかしら。また聴きに行こう。\n","date":"2014-12-03T14:17:38Z","image":"/archives/1796/img/DSC00474.jpg","permalink":"/archives/1796/","title":"洗足学園音楽大学 FUYUON! 2014 クラシックギターコース演奏会に行ってきた"},{"content":"ソニー吹奏楽団さんのファミリーコンサートに行ってきた。11 月 29 日、土曜日、会場はめぐろパーシモンホールの大ホール。\n演奏会らしい演奏会に足を運んだのが一か月ぶりくらいになってしまった。やっぱり生音はよい。ライブはよい。生きた音楽に触れるのはだいじ。\n吹奏楽の演奏会に行っておいてアレだけれど、鍵盤ハーモニカがべらぼうにうまかった。バンドネオンに比べるとリベルタンゴにはちょっと上品できれいすぎる感じもあったけれど、でもうまかった。\nあとはブラボーブラス。ソニーさんはわりとクラシック音楽っぽいスタイルというか緻密な演奏をしている印象なので、遊ぶ曲よりも丁寧に音を作れるこういう曲の方が似合う気がする。定期演奏会だとそういう曲がたくさん聴ける。\nはとポッポはネタ枠だったけど、ドヴォルザークの第八番風とショスタコーヴィチの第五番風のが個人的にはツボだった。いい展開。\nこのコンサート、ファミリーコンサートという名前の通り、未就学なお子さまもぜひどうぞというスタンスの演奏会。というかむしろそういうひとたちこそメインターゲットとして設定されているのだと思う。\n演奏会って、呼びたい客層が明確でないと、万人受けを狙って逆に主軸が見えにくくなりがちで。その点、この演奏会は対象が明確で、曲もとてもわかりやすかった。パンフレットにも構成にも司会にも “お子さま向け” の要素を取り入れていたし、客層を決めてそれに合わせた準備と運用をするのってやっぱり大事よね、と。お勉強になった。\nひさしぶりのよい生音だったし、満足した。\n","date":"2014-12-02T14:31:48Z","image":"/archives/1789/img/DSC00483.jpg","permalink":"/archives/1789/","title":"ソニー吹奏楽団のファミリーコンサートに行ってきた"},{"content":"ライブ@ポトス 11 月 23 日、土曜日、ポトスさんでのライブに出てきた。Rubinetto として、これで三回目。\n前回と同じ四曲に新しくソノダバンドさんの Manic Street を追加して、全部で五曲の合奏と、ソロを一曲、のプログラム。\n楽しかった、とても。あいかわらず。\n個人的にこの団体は『楽しいは正義』を地で行くところであってほしい（というかありたい）ので、そういう意味でもとてもよい時間だったと思っている。楽しかったら勝ち、みたいな、そういう感覚。\nだから勝ちでした。よかったよかった。\n共演者のみなさま、お店のみなさま、ありがとうございました。終わったあとのわちゃわちゃに居られなくて残念でした……。\nRubinetto 近況 さて、そんな Rubinetto、所属メンバ ページの通り、徐々にメンバが増えてきて、当初 5 人だったのがすでに 13 人。\n4 月に 1 人、7 月に 2 人、8 月に 1 人、10 月に 2 人、11 月に 2 人。\n多摩高校ギターアンサンブル部の OB から始まったけれど、今では相模大野高校クラシックギター部と大野南中学校ギター同好会と東京大学古典ギター愛好会のそれぞれの出身のみなさまが集まっている。パートにカホンも増える、予定。\nまだまだメンバも募集しているので、興味があるひとはお気軽に。\nそして 2 月と 3 月に 1 回ずつ、単独公演を計画中。\n日も場所もなにも決まっていないので、場所が取れるのかもわからないのだけれど、お楽しみに、ということで。\n","date":"2014-11-30T10:20:17Z","image":"/archives/1774/img/DSC_0021.jpg","permalink":"/archives/1774/","title":"ポトスでのライブと、Rubinetto 近況"},{"content":"10 月 22 日、水曜日。会場は銀座のヤマハホール。会社はもちろん早退。\n衝撃的なすばらしさ。これはギターである。まぎれもなくギターである。ぼくなんかには正しくその価値を理解できないのではという感覚に陥る。そしてたぶんそれは事実。\nいろいろなギターの音を聴いてきたつもりではあったけれど、こんなにダイナミックレンジのひろい楽器だったっけと、こんなにいろいろな音の出る楽器だったっけと。ぼくの知っているギターとは何だったのかと。\nいまさらそう思った。今まで聴いたことがない音だった。強烈に見せつけられたし、魅せつけられた。\n踊るような指遣い、めまぐるしくかわる音の表情、圧倒的な没入感、最後の一音の崇高な響き。\n陳腐な言葉ををいくら並べたところでどうにもあの音楽を形容できるものではないのだけれど。\n気持ちがよすぎた。音色に酔うし音楽に酔う。さいこうだった。\nよい音だった。ほんとうによい音だった。\n練習しよう。まずは絶望するところから。\n","date":"2014-10-24T15:21:09Z","image":"/archives/1761/img/DSC09185.jpg","permalink":"/archives/1761/","title":"アルヴァロ・ピエッリさんのギターリサイタルに行ってきた"},{"content":"『宮川彬良の』まで含めて公演の正式なタイトルの一部なだけで、決して呼び捨てにしているわけではない。\n10 月 19 日の日曜日、会場はせたがやパブリックシアター。某試験の日だったので、最初の 45 分くらいは遅刻して観られていない。\n出演は宮川さんを『所長』として、『特別研究員』に池田理代子さん、『研究員』に INSPi のみなさま。\nベートーヴェンの交響曲第五番と交響曲第九番を題材として、音源を聴きながらそれを宮川さん流の解釈で解説（体裁はあくまで “研究”）していく、みたいな内容。音源は生演奏ではなくて CD だった。惜しい。\n音楽を解釈すること自体をエンタテインメントとして仕立てあげた、という印象で、NHK の教育番組の公開収録を観ているかのような感覚。ところどころでベートーヴェンにまつわる研究員さんたちの会話や INSPi さんのライブパフォーマンスが入る。『日立の樹』はさすがに歌い慣れているようでばっちりのたのしい音楽っぷりだった。\n最後はこの日のために練習を重ねた一般区民（特別研究員）さんたちを含めた全研究員の合唱 “歓喜の歌” でしめ。\n出演者のみなさん、音楽家でありながらも、それ以上にエンタテイナなんだなあと。\n解釈に是非はあるかもしれないけれど、そもそもそういえば音楽の解釈って自由でよいんだなあと、そう思えてたのしい時間だった。\n","date":"2014-10-22T17:11:21Z","image":"/archives/1748/img/DSC09182.jpg","permalink":"/archives/1748/","title":"宮川彬良のせたがや音楽研究所 #2 に行ってきた"},{"content":"10 月 11 日、土曜日。第 3 回岡上分館カフェコンサートというイベントに、Rubinetto として出演してきました。会場は岡上分館、体育室です。\n演奏のようすをまるっと YouTube にアップロードしたので、せっかくだしそれぞれ説明をいれつつ全曲を紹介してしまうことにします。\n演奏した曲 おすすめ順に紹介したいので、まずは三曲目に演奏した『さんぽみち』から。\nメンバのひとりである Haracem のオリジナル曲。もともとはアコースティックギターソロ用の曲で、Rubinetto の編成に合わせて編曲してくれたのがこれです。\nさんぽをするときのいつもとちょっとだけ違うとくべつな期待感とか冒険心とか、そんな空気にあふれていて、さんぽってたのしいよねって思える、よい曲です。弾いているとテンションがばくはつします。\n編曲によって中間部のソロとか転調とかいろいろな要素が加わって、原曲からひとまわりもふたまわりも物語性というか表情がゆたかになった印象です。たのしいです。\n次は、最後に演奏した、押尾コータローさんの『翼 ～you are the HERO～』。\n押尾コータローさんを知るひとであれば誰もが弾いてみたくなるであろう名曲。これも Haracem の編曲で、原曲を知っているとよい意味でいろいろと裏切られる展開、たのしいです。\n残りの二曲は、Rubinetto が結成して最初に練習に取り組んだ曲です。葉加瀬太郎さんの『Born to Smile』と、ソノダバンドさんの『道草のススメ』。\nどちらもメンバがいちばん弾きなれている曲で、Rubinetto として最初にひとまえで演奏したとき の曲目もこの二曲でした。\nそんな四曲。\nいまのところ、この四曲が Rubinetto の持ち曲のすべてです。\n岡上分館というところ 小田急線の鶴川駅から徒歩圏にある、麻生市民館の分館です。\nRubinetto という団体ができて、練習場所をどこか借りようとなったときに、初めて存在を知りました。\n白状すると最初にここを選んだ理由は “値段” だったのですが、いまではすっかり愛着がわいています。感覚は “母校” のそれに近いですね。\nまわりには何も無くて、すぐ横に畑がひろがるようなふるぼけた地域ですが、人工的な騒音のないのんびりとした空気のなか、雨の日は水の音、晴れの日は鳥の声、夕方は虫の音にかこまれてゆったりと練習できるのも、今ではなかなか貴重な気もします。いつかここで、動画を撮りたい。\n『道草のススメ』とか『さんぽみち』を演奏している最中に、ふと外の鳥や蝉の声に耳を向けて、ああ、いいなあと、そう思えるのも、岡上分館のよいところだと思っています。\nカフェコンサートというイベント 主催は岡上分館ですが、『平成 26 年度麻生市民館岡上分館市民エンパワーメント研修 みんなでつくるカフェコンサート』というプログラムのもとに集まったみなさまで運営されている…… ようです。コンサートの企画や運営を、実際に『カフェコンサート』をつくることを通じて学ぶ、ようなものだと思います。\nそういう背景なので、いつぞやの練習後にスタッフさんからカフェコンサートの存在を知らされて、出演しないかと声を掛けていただいたことがきっかけで今回のこのような演奏な場が実現したわけですが、ほんとうにありがたい限りです。\nこういう手作りの演奏会、ぼくはとてもだいすきで。\n下はちびっこから上はぼくの父よりも上と思われる方々まで、はばひろい層の方々が、朝からたのしそうに会場を設営したり飾り付けをしたりしている様子をみていると、音楽の理想的なかたちってこういうのかもしれないなあと、なんていうか、しあわせな気分になります。\n出演は Rubinetto をふくめてぜんぶで五団体で、Rubinetto 以外のみなさまも岡上分館を練習場所に活動しているようでした。\n個人的なことをいえば、高校時代の部活の同期のお母さまやら多摩高校合唱部の OB さんたちやら、いろいろとつながりのある方々が他団体として出演しておられたことも、このコンサートをすきになった理由のひとつです。\nRubinetto という団体 レパートリィがようやく四曲になって、なんとか 20 分の枠ならもたせられるようにはなってきました。さらに持ち曲を増やすべく、これから新曲にも取り組むつもりです。単独の演奏会の開催を目指しています。\nギター部を卒業したみなさま、部活時代とは少し違った、こういうカジュアルなギターを楽しみたいみなさま、ぜひいっしょに弾きましょう。\nうれしいことに、このコンサートがきっかけで、すでにひとり、某中学校ギター部の OB さんが新規加入の意思を表明してくれています。\nまずは見学にどうぞ。うまいへたなんて、どうでもよいことです。たのしいことは正義なのです。\nつぎの練習は 10 月 26 日の日曜日、朝 9 時から、場所はこの岡上分館 です。興味があるかた、ついった でも メール でもご連絡ください。\nさいごに 岡上分館のみなさま、運営スタッフのみなさま、共演した他団体のみなさま、そして雑な呼びかけにも関わらずご来場いただいたみなさま、ありがとうございました。\nギター部 OB のみなさま、見学のご連絡お待ちしています 😛\nちょーたのしかったです 🙂 また出たいので、またぜひ！\n","date":"2014-10-16T12:00:50Z","image":"/archives/1712/img/DSC09108.jpg","permalink":"/archives/1712/","title":"岡上分館のカフェコンサートに出たよ（動画もあるよ！）"},{"content":"たのしかった！ です！\n前回 はオープンマイクだったけれど、今回はしっかりとしたライブでした。9 月 28 日、日曜日。\nRubinetto としてひとまえで演奏するのはこれで二回目。前回は持ち曲が二曲しかなかったけれど、ようやく四曲までレパートリィが増えまして。\nいやはや、たのしいものですね。\n頭を空っぽにして、むずかしいことはなにも考えないで、完全にノリだけでぎゃんぎゃんに弾く。\nという弾き方ができる団体であり曲であるので、わりとストレートに手放しでおもいきりたのしめたのでした。\nきれいなホールで芳醇なひびきに身をゆだねるのもたまらなくだいすきだけれど、こういう、“コンサート” というより “ライブ” って感じの空気も、これはこれでだいすきで。\nなんていうか、ああぼくはいまギターを弾いているんだなあ、みたいな気になれる。すばらしい。\nぎゃんっぎゃんに弾いているぼくらの演奏が聴いてみたいみなさま、10 月 11 日に鶴川駅近くの岡上分館で会いましょう。詳細はこちらから。\nカフェコンサート＠岡上分館 | RUBINETTO むしろいっしょに弾いてみたいとおもったみなさま、ぜひ入りましょう。たのしいです。ご連絡お待ちしています。\nメンバ募集中！ | RUBINETTO ポトスのみなさま、ほかの出演者のみなさま、ありがとうございました。\nまたぜひ。\n","date":"2014-09-30T14:27:28Z","image":"/archives/1690/img/DSC09025.jpg","permalink":"/archives/1690/","title":"宿河原のいつものお店、ポトスのライブに参加してきた"},{"content":"今年も行ってきた。9 月 22 日の日曜日、相模原中等教育学校の文化祭、蒼碧祭の文化部門。\nただの視聴覚室だから音楽用の場所ではないのだけれど、それなりに防音がしっかりしていて外の騒音がかなり抑えられているので、“文化祭” らしからぬ音響で聴けるのはこの会場のよさのひとつ。\n平たくいうと、やっぱりうまいなあ、ってかんじ。ステージにもかなり慣れていて安心感がある。\nギター合奏歴二年から五年のひとたちの集まりだし、そりゃあ当然感覚も育つだろって話ではあるのだけれど、それにしたって中高一貫の強みがうまく出ている気がする。年齢差が大きくなるのはもちろん弱みでもあるのだけれど、それを踏まえても組織としてのバランスはとてもよさそう。\nポピュラ音楽ってどうしても歌詞があってこそのものも多くて、でもギター合奏でやるなら歌詞は表現できないから、いかにメロディを歌詞から卒業させてひとり立ちさせられるかがおおきな課題。聴き手が歌詞を覚えているような有名な曲であれば『聴き手の脳内で勝手に歌詞が再生されることで補完されて曲として完成する』っていうこともなくはないのだけれど、それこそ個人差だから意図して狙うべきではないし。\nつまるところ、ギター合奏でその曲を演奏する必然性をどう持たせるか、という話になるのだけれど。\nやさしさに包まれたならとか、君をのせてとか、その辺のもうド定番で超有名なものは、いまさらそれを問いかけてどうするのってところまで枯れている感もあるので、細かいことは気にせずに好きにうたえばたぶんきれいにできあがるタイプの曲。\nと思って聴いていたら、実際何の不安もなかったよね。こういうのをさくっと不安なくつくるあたり、基礎力の違いだよなあとも。悪くいえば、そもそもの選曲に目新しさはないし、できあがる演奏も予想通りでしかない、とも言えるのだけれど。\n今回の曲目の中では Surge III の第三楽章がいちばん好きだった。うねりっぷりもだけれど、指揮者の H くん、お世辞抜きに観るたびにめきめき腕が上がっている感がすごい。ぐんぐん伸びるうえに来年も続投予定とのことで、これからも化けそう。楽しみ。\n序奏とアレグロは、個人的には表情の変化を暑苦しいほどに愚直に音にしてぶちまけるような演奏が好きなので、ちょっと冷静すぎた気も。考えて弾くのもだいじだけれど、すべての表現が考え抜かれた計算尽くのものになってしまうと、それはそれでつまらない。もっとも、たぶん芳醇な響きがあってこその曲でもあるから、根本的に場所との相性がすこぶる悪そうというのもあったのだけれど。\nあとはからあげを食べたり、たこやきを食べたり、わたあめに並んだり、写真館をみたり、文化祭らしく文化祭を楽しんだ。いろいろなひとにひさしぶりに会えたし、たくさんのひとに声をかけてもらえるのはうれしいものです。\nそして恩師にもお会いできた。じつは文化祭に来た目的の半分はこっち。\nもともと多摩高校で教えていた先生。相模原中等教育学校で教鞭を執りはじめて六年目。\nぼくの人生でほんとうに数少ない、“尊敬” という表現を遣うことを微塵も厭わない先生、“恩師” と表現することに一切の躊躇をしない先生のひとり。高校当時、話した時間はそう多くはないのだけれど、その短い時間でだってぼくの生き方が刺激を受けるには充分すぎるほどで。\n卒業して以来の再会で、会えば話題はやっぱり学校のこと、教育のこと。学校は “生き方” を学ぶ場所だと、ぼくは教育についてはしろうとなのでそのくらいの感覚でしかものを言えないのだけれど。\nあいかわらず哲学は健在で、絶望的なまでの頭のよさと回転の速さには到底追いつける気がしないなあと思いながらも、ひとときの会話を楽しんだ。\nまた会いに行こうと、そう思った。\n","date":"2014-09-25T12:00:52Z","image":"/archives/1670/img/DSC08834.jpg","permalink":"/archives/1670/","title":"相模原中等教育学校の文化祭で、クラシックギター部の演奏を聴いてきた"},{"content":"もう何度目かわからないけれど、今年もぼくが愛してやまない母校こと多摩高校の文化祭へ、ぼくが愛してやまない部活ことギターアンサンブル部の演奏を聴きに行ってきた。\n9 月 6 日、土曜日。\n行ったのは一日目の第一回目の公演。例年通りほかの OB 勢といっしょに外の階段に居座る。\n何度も言っているけれど、部活って『楽しいは正義』の最たる例で、活動内容よりも実は部活という活動形態そのものにものすごく価値があるものだと思っている。\n高校を卒業してからギターを続けるひとが多くないのって、きっと『ギターがすき』というより『部活がすき』というひとが多かったからなんだろうなっておもってる。なんていうか、そうね、部活っていいよね、ほんとうに\n\u0026mdash; くろい (@kurokobo) September 9, 2014 入部したきっかけは “ギターであること” だったかもしれないけれど、あるときからギターが “部活を楽しむための手段のひとつ” になることは、たぶんよくあることなのだろう。時間を共に過ごす仲間がいることは、それほどまでに “部活” を魅力的にする。\n部活は、だからどうしようもなく、よいものだと思う。だからぼくは好きだ。好きだったし、いまでも好きだ。\nそんなことをぱやぱやと考えながら、全力で部活を楽しんでいる現役さんたちを眺めていた。外の階段は、部活が部活であることがよく見える位置でもある。\n見えるけれど、音はあまり聴こえない。\nもちろん少しは聴こえるし、廊下に首を突っ込めばもちろんもっと聴こえるけれど、でもなんていうか、全力で部活をしている現役さんを見ていたかった、ということもあって。\n重奏のうち一団体、音がすばらしくよくでているところがあった。その場にいた三年生にパートリーダ重奏か何かかと聴いたら、『えっと…… つよいひとたちです、こんみすさんとか』という。よい表現だと思った。つよいひとたち。\nそれ以外の演奏は、そんなわけであまり聴けていない。それでも、Surge もパガニーニも、コンクールの演奏よりもぼくはすきだった。\n力まずにスマートに弾くことが、この代にはよく似合う。“笑顔” と書かれた紙を掲げて、それでひとを笑顔にできるのは、つまりは彼の人望だ。\nそんなこんなで、総じて部活っていいなあと思った、そんな土曜日だった。\n","date":"2014-09-10T16:36:08Z","image":"/archives/1653/img/DSC08475.jpg","permalink":"/archives/1653/","title":"多摩高校の文化祭で、ギターアンサンブル部の演奏を聴いてきた"},{"content":"行ったことがない高校の文化祭に行くときのわくわく感、久しぶりだった。\n8 月 31 日、日曜日。池袋から東武東上線にゆられること五十分（ちかい！）、埼玉県立坂戸高等学校の文化祭、やなぎ祭へ。ギター部の演奏を聴いてきた。\n最近では NKG のコンクールでおなじみの坂戸高校ギター部さん。昔は JAEM のにも出ていたし、JGA のにも 2009 年に一度。“独立系” とでも言うか、勝手に親しみを持っていたこの方々。\nひとことでいうと、定期演奏会に行きたくなった。体育館では聴けない音が聴きたくなった。\nマジカル☆プレリュードと、ARSNOVA 組曲の第一楽章が、全日本ギターコンクールに向けた二曲。これ以外は映画音楽かポップスというとても文化祭らしい選曲。\n文化祭での演奏って、どこの学校でも音響条件はよくなくて、だから音を聴くというよりはどちらかといえば割り切って空気とか雰囲気とか学校そのものとか、音以外のところのを楽しみに行くようなもの。\n今回も会場は体育館だったし、ぼくが立っていたのもうしろの方だったから、まともには聴こえないのを覚悟していたのだけれど、思っていた以上に “こなれていた” ので、これはぜひきちんとした演奏会でしっかりと音を聴きたいなあと、そう思った。\nコンクール曲ではアルトギターもバスギターもギタロンも弦バスもでてきたけれど、大多数がプライムギターというめずらしい編成。パート分けも特殊そうで、コントラバスギターは見当たらない……？\nそれでも構成のアンバランスさをあまり感じなかったので、体育館ていう悪条件下であることも考えると、音を作る感覚は鋭そう。やっぱり体育館でないところで聴いてみたい。でもひとついうなら、しかたがないとは思いつつ、打楽器はちょっと大きすぎた……。\nそして全体的におとなしいといえばおとなしい。空気も雰囲気も堅実で真面目な印象で、きれいなクラシック曲がよく似合いそうなのだけれど、それは逆に派手さや特徴めいたものを隠してしまうものでもあって。\nポップスや明るくて元気な曲を中心に並べるのであれば、はちゃめちゃにうたうとか、エネルギィを爆発させるとか、がつがつに欲のままに弾くとか、もうちょっとそんなテンションに近付けられたら生まれ変わりそうな気はした。\nもうひとつ進行面、たとえば司会の礼とかメンバ紹介とか入り捌けとか、そういうところがとても厳格で統率のとれた礼儀正しい動きだったので、司会のしゃべりのテンションとのギャップあって、楽しげになった空気が細切れにされてしまう感があったのはちょっと残念。このあたり、司会のテンションを基準に時間の流れを作ってみると、風通しもよくなるかもしれない。好みの問題でもあるけれど。\nしかし何を言ったところで、やっぱり体育館だっていうのが惜しい！ 脳内で補完するにも限界があるし、やっぱりどこかのホールで静かに聴きたい……！\n定期演奏会は五月のようなので、来年おじゃましたいところ。生音でのていねいな音作りは得意そうだし、JGA のコンクールにももういちど出てみてほしいなあとも思った。評価軸が違うコンクールが別にあるっていうのは、こういうときにおもしろい。\n終わったあと、あまりこない地域なので周辺を散策。ゆったりのんびりとした風景で、視界の広さがうれしかった。\nこういうところに学校があるのはうらやましい。晴れた夏の日に広い田んぼの横を自転車で駆ける姿は、ぼくがどこかで憧れているもの。\n帰り道、ついでに川越市駅で途中下車。十年くらい前に川越高校古典ギター部さんの定期演奏会を観に来て以来。\nひとがとても多いなか、退色した写真のような町並みを眺めつつ、長いふ菓子を小脇に抱えて、帰路についた。\nそんな遠足。たまにはよい。\n","date":"2014-09-03T14:40:04Z","image":"/archives/1567/img/DSC08325.jpg","permalink":"/archives/1567/","title":"坂戸高校の文化祭で、ギター部の演奏を聴いてきた"},{"content":"ひとことでいうと、メンバ募集中です。Rubinetto、ルビネットといいます。\nギター部卒業しちゃって、もうすこしギター続けたいけど、どこの社会人団体もだいたいレベル高いしなー、あそこまでガチにやりたいってほどでもないんだけどなー、ちょうどいいところないなー。\nとか、\nギター部卒業しちゃって、ほんとうはもう少しギター続けたかったけど、気軽にぴょろっと入れそうなところがなくて結局あきらめたんだよねー。\nとか、そんなひとたちの集まりです。\nそして同じことを思って同じようにギターを諦めた、あるいは諦めかけている、そんなひとたちをもっと集めて、いっしょにギターを弾こうとしています。\nだから講師は居ません。ぜんぶ自分たちでつくります 練習場所はそこらへんの公民館の会議室とかです とくにコンクールに出る気はないです。でもただの演奏会はなるべくたくさんやりたいです クラシック曲は弾きません。インストゥルメンタルばっかりです ぶっちゃけ、下手でも楽しければいいじゃんって思ってます そんな団体です。興味がある方はぜひ 中のひとのだれか にお声かけください。よくわからなければ お問い合わせページ からめーるくれればどうにかします。\n近いうちに、以下のイベントがあります。参加自由なので、お気軽にどうぞ。\n9 月 7 日（日）の 17:30 から、公開練習。岡上分館にて 9 月 28 日（日）の 18:00 からミニライブ。ポトスにて 10 月 11 日（土）の 13:30 からカフェコンサート。岡上分館にて 入るか入らないかは、見に来てから考えればよいのです。\nできたてでレパートリィは少ないけれど、でもやっぱり、ギターはたのしいんだって、そう思いながらやっています。\nちかいうちに演奏の動画を YouTube あたりにぶん投げるつもりなので、それもお楽しみに！\n","date":"2014-09-01T12:00:06Z","image":"/archives/1573/img/DSC08241.jpg","permalink":"/archives/1573/","title":"新しいギター演奏団体、Rubinetto ができました"},{"content":"全国学校ギター合奏コンクール 2014。併催、第 25 回 JGA ギター音楽祭 Kyo-en in 東京 2014。\n今年もこの日がやってきた。\n8 月 25 日、ミューザ川崎シンフォニーホール。前身の全日本学生ギターコンクールを含めると、ぼくの人生で十二年目のこのコンクール。年にいちどの夏の楽しみになって久しい。\n行ってきたというとお客さんのようだけれど、実のところだいたい毎年、裏方のひとりとしてぼくは舞台袖に居る。\n袖では見えない風景も、袖では聴けない音楽も、袖では味わえない空気もあるけれど、袖でしか見えない風景も、袖でしか聴けない音楽も、袖でしか味わえない空気もある。\nぼくはそういうものがだいすきで、だからぼくはぼくがそこに居られることがすごくうれしい。\n会釈するだけ、ほんのひとことふたこと言葉を交わすだけ、あわただしく動き回っているとどうしてもそれだけしかかなわないこともあるのだけれど、それでも出場するいろいろな学校に、友人知人が居る。自分が出場していたころ、あるいはまだぼくの世界が狭かったころ、どうしたって母校びいきで、いうなれば “多摩とそれ以外” というくくりでしか観られなかった世界も、今ではもうまるで違うもの。\n“やりたいからやる” という、人類最強のモチベーションの表出であるところの、部活。\n全国にちらばるたくさんのそれが、コンクールとコンサートという似ているようで四分の三くらい対極にあるふたつをそれぞれで繰り返すわけで、どうしたって創発的に進歩するはずだし進化するはずだし発展するはずだし発達するはずで。年々レベルが上がっているとよく言われるけれど、だからある意味でそんなことは当たり前で、逆にそうでなければこの世界に未来はない。\n順位が決まって、賞が決まって、今年のコンクールはそれで終わる。どこが勝った、どこが負けた、うれしい、くやしい、いろいろあるし、友人たちがわんわん泣いているのを観て、ぼくだっていろいろ思うけれど。\nこの日、この学校がこういう演奏をした。その演奏は、この審査員の方々にこう評価された。その結果、こういう順位が付いた。それはもうひとつの事実で、あしたからのギター合奏は、その事実の上でしかできないし、その事実からしか生まれない。あしたのギター合奏は、あしたをいきるひとがつくるし、一年後のギター合奏は、一年後をいきるひとがつくる。\nぼくが出ていた頃と今年のこれとを近似直線で結んでみたところで、この先の百年二百年の予測が立てられるわけでもない。それでも未来は過去がないとつくれないから、この日は未来のギター合奏を作るためには必要不可欠な一日で、それを演奏者として作り上げた七百名はやっぱり偉大な方々なのだと思う。出場することでしか得られないものは、溜め息がでるほどに大きいし、目眩がするほどに大きい。いくら裏で動いていようと、それはぼくには得られない。\nこれから、この世界はどうなっていくのかしらと、どういう未来がくるのかしらと、少しばかりの不安とともに大きな期待を持ちながら。できればぼくは、この世界の未来の姿を、自分の目と耳で観て聴いていたいと思う。\n出場されたみなさま、よい音楽とよい時間をありがとうございました。\nぜひ、それぞれの人生を。\n","date":"2014-08-27T13:49:21Z","image":"/archives/1547/img/DSC08263.jpg","permalink":"/archives/1547/","title":"全国学校ギター合奏コンクール 2014 で、ギター合奏のことを考えた"},{"content":"8 月 22 日、某社主催の某イベントに行ってよいことになったので行ってきた。会場は都内某所。行ったことそのものは記録しておきたいのだけれど、いろいろ配慮して伏せているあたり、お察しいただきたく。\n会社を定時で飛び出して向かったこのイベント。イベントの性質上お子さまを連れた方々多かったけれど、おじいちゃんおばあちゃん世代も居て幅広い。いろいろな企画があったようなのだけれど、夜の音楽の部分だけ狙って参加。\nさいしょに聴いたビッグバンドさんは総じておしゃれでオトナな雰囲気で、『じぶんたちの好きな曲』を選んできたようなプログラミング。\n若干緊張していたのかなかなかジャジィな空気にもなりきれていなかった感があったけれど、時間が経つにつれてこなれてきて、三曲目（曲名わすれた……）ではサックスのソロのふわふわとした揺れがとても気持ちのよいカンジに。あれは好きだった。\nお子さまが置いてきぼりになっている感もあったけれど、ジャズでお子さまの心をつかむのは難しそうだし、親世代向けという割り切りはそういう意味ではアリなんだろうなあと。ふだん単独で演奏会もしているようなので、いっかい行ってみようと思った。\n一転して次の吹奏楽さんは、お子さまも完全に対象に含めてきた本気のプログラミング。吹奏楽の定番曲を入れながらも、季節モノ（夏の定番ポップスのメドレ）、流行モノ（ようかい体操第一）などをじゃんじゃん入れてきて、『会場のみんなもいっしょに踊ってみよう！』と攻めてくる。\n演奏がうまいのはもちろんなのだけれど、お子さまたちがよろこんでうたっておどってはしゃいでいるのを見ると、うまいプログラムだなあと思わざるを得ない。選ぶ曲もそうなのだけれど、ようかい体操第一でお子さまをがっつり引きつけた状態で吹奏楽らしさ全開の宝島を持ってくるとか、そういう順番もよく考えられている。すごい。\n最後は合唱団さんも混ざって、小学校の音楽の授業でやるような（この選び方もうまい！）合唱曲や流行の Let it go（お子さまも大合唱！）、ストレートに明るい曲（たのしい！）など何曲か披露して終わり。\n曲の選び方はすごくだいじだなあと強烈に思った夜。たのしかった。\n","date":"2014-08-25T12:02:59Z","image":"/archives/1537/img/DSC08194.jpg","permalink":"/archives/1537/","title":"某社の某イベントでビッグバンドと吹奏楽を聴いてきた"},{"content":"8 月 18 日、月曜日。おしごとを午前中で切り上げて、途中から聴きに行ってきた。会場は相模女子大学グリーンホール。\n全国学校ギター合奏コンクールに出場する団体のうち、神奈川県内で活動する方々が集うこの演奏会。毎年コンクールのすこし前に開催されて、幹事は各校で持ち回り。\n後ろの三団体しか聴けなかったので、特定のどこかについての言及は控えるけれど、センパイの背中を見て育ったところ、自分たち独自の世界を作っているところ、学校ごとの違いもさることながら、同じ学校のなかでも要素によっていろいろな色が見えて、ああほんとうにもう一年経ったんだなあと。\n聴くところによると、ぼくが聴けなかった前半組の演奏も目を見張るものがあったようで。\n一週間後のライバル同士で、本番一週間前にして手の内を明かしあう、とても刺激的な場。だからあしたからの一週間が、成長曲線でいえばいちばん伸びる期間。\n次の日曜日、ミューザの舞台が、とても楽しみ。ぼくも袖でうるうるしたい。\n","date":"2014-08-18T15:36:52Z","image":"/archives/1527/img/DSC08192.jpg","permalink":"/archives/1527/","title":"ギターサマーコンサート 2014 に行ってきた"},{"content":"SC-88 を友人からもらった。SC シリーズだと SC-88Pro っていう通称 “ハチプロ” が有名だけど、これはその 2 年前に発売された、88 系列の最初のモデル。\nミュージ郎の時代だ、ミュージ郎の。\n今でこそ mp3 なんていう便利な PCM 音源が台頭しているけれど、インタネット聡明期のピーガガガ時代に、インタネットの “向こう側” からダウンロードできるほんの数十キロバイトの MIDI ファイルに、いったいどれだけのヒトがどれだけのユメをみたことか！！\nスピーカをオンにしたままネットサーフィン（死語）をしていたら MIDI ファイルが自動再生されるページを踏んでしまって爆音にびっくりするとか、巧妙に埋め込まれた MIDI ファイルをどうにかダウンロードしようとソースコードから JavaScript まで追いかけるとか、そういう時代。\n当時の JavaScript は、右クリックを禁止したりステータスバーにメッセージを流したりページを開くと『ようこそくろいさん！ 11 回目の訪問です！ 夕焼けがきれいな時間帯ですね！』って出てきたりページ遷移にきらびやかなエフェクトをつけたりマウスカーソルにオリジナルキャラクタが常にくっついてきたりクリックすると花火が上がったり、そういう技術だった気もする。\nそれはいいとして、とりあえずこの SC-88 を鳴らしてみたいので、セットアップ。\n接続とリセットと初期設定 本体の [COMPUTER] スイッチを [MIDI] にして、使っているオーディオインタフェイスの MIDI 出力を SC-88 の [MIDI IN A] に MIDI ケーブルでつなぐ。\nヘッドホンで聴くだけでいいならこれで終わり。必要があれば RCA な [OUTPUT] をオーディオインタフェイスの入力に戻してやる。\n電源を入れて、謎な設定が残っているかもしれないので、ファクトリリセットする。\n[SELECT] を押しながら [INSTRUMENT] の [←] と [→] を同時押し 確認が出るので [ALL] で承認 処理が終わったら電源を入れなおして完了 デフォルトでは SC-55 互換音色マップが有効になった。そういうものらしい。\nMIDI 出力デバイスが選べるプレイヤならここまででひとまず音は出る。Windows の標準出力を変えたい場合は、Windows 7 なら Putzlowitschs Vista-MIDIMapper（声に出して読めない）などを突っ込んでよしなに設定してやる。\n肝心の音はどうなの Windows に昔からもともと入っている C:\\Windows\\Media\\town.mid で比較。\n最初に Windows 7 の標準の、Microsoft GS Wavetable Synth。\n次に今回もらった SC-88。55 互換はオフで、88 ネイティブな状態。Muted Gtr が弱いけど全体的な雰囲気はこれが好き。\nSC-88 で 55 互換を有効にするとこうなる。\n実を言うと VSC3（Virtual Sound Canvas 3）のライセンスは持っているので、これでも鳴らすとこうなった。88Pro 互換モードに設定。SC-88 の 55 互換モードとよく似ている。VSC は x64 環境だと動いてくれないので普段使いには厳しい。VSTi 版は持っていない。\nVSC あるならハードウェアいらないのではっていうのも理解はできるのだけれど、でも物理的なモノってやっぱり好きなのよね。\nオマケで、TiMidity 版。SGM-V2.01 っていうサウンドフォントを丸ごと突っ込んである。\nTiMidity、高校時代にカスタマイズにハマっていた記憶が。\nSFC の AO 入学生向けの入学前レポートで、当時のぼくがネタとして取り上げてもいた。今みたら、\nこのように、それぞれの楽器ごとにサウンドフォントを割り当てることで、MIDI 再生時に TiMidity++ が該当するサウンドフォントを参照し発音する。サウンドフォントごとにバラバラである音量などのパラメータも、ユーザ側で任意に調整できるようにされており、この例のように音量（amp）、パン（pan）など、他にもピッチやオフセット等の調整用パラメータが用意されている。\nとか、\nインターネット上にあるサウンドフォントは玉石混合であり、納得のいく音色を持つサウンドフォントを探すのは非常に難しいことであるが、この玉石混合から『玉』を探し出すのがこのカスタマイズの醍醐味でもある。また、インターネットの一部ではユーザがカスタマイズした設定ファイルが公開されていることもあり、これを使用すれば比較的簡単に高音質再生環境を実現することが可能である。\nとか書いてあった。なつかしい。\n使い道 正直何も考えていないのだけれど、実家に MIDI キーボードを置いてきているのでそれを持ってくればいろいろ遊べそうだなーとか考えている。\n既成の MIDI ファイルを綺麗に鳴らすためのハードウェアってわけではないので、本来の使い道としてはその方が正しいのだけれど。\n","date":"2014-08-10T13:31:41Z","image":"/archives/1502/img/DSC07944.jpg","permalink":"/archives/1502/","title":"Roland の SC-88 をもらったので、21 世紀の今あらためて MIDI を聴く"},{"content":"8 月 3 日、日曜日。NHK 交響楽団の、N 響ほっとコンサートに行ってきた。\n夏休み特別公演と書かれていることからもわかるように、イベント自体は夏休み中のおこさまたち向け。\nおこさまたち向けのイベントではあるけれど、おこさまたち向けだからって手を抜くなんてことは当然ながらまるでなく、むしろ平時の定期演奏会よりも熱が入っていた印象でとてもよかった。\nおこさまたちってすごく正直で、つまらなくて飽きたらすぐに興味を失ってほかのことをしだすわけで。\nだからなのかわからないけれど、奏でられた音楽は、これでもかというほど強烈にオーケストラの魅力をアピールしてきていて、かっこうよさを全力で見せつけてくるような、そんなアツい演奏だった。\nねちっこくて暑苦しいほどにストレートに本気で、それがさいこうに爽快。あそこまで重くて壮大な終わり方をする『キエフの大きな門』は初めて聴いたよ！ かっこうよかったー。\nオーケストラの魅力を伝えようとして企画されたコンサートで、オーケストラかっこいいなーって思えたので、だからとても気分がよかった。\n定期演奏会みたいなかっちりしたのもあれはあれでいいけど、こういう気軽な雰囲気で本格的な音楽が聴ける演奏会っていうのもよいですね。\n","date":"2014-08-05T17:20:23Z","image":"/archives/1478/img/DSC07936.jpg","permalink":"/archives/1478/","title":"NHK 交響楽団の N 響ほっとコンサートに行ってきた"},{"content":"またまた吹奏楽を聴きに。7 月 13 日、日比谷公会堂。\n同じ日に洗足音楽大学のクラシックギターコースの演奏会もあって、そちらも魅力的ではあったのだけれど、残念ながらハシゴはかなわず。\nのんびりめに家を出たら、東京隆生吹奏楽団さんの演奏中に到着。\n着いていきなり観たこの方々がいきなりレベル高くてテンションうなぎのぼり。フルートのソリストさんの身体のつかいかたがイケイケでかっこうよかった。\n音楽的情動に素直に従った派手な身体の動きってぼくはすごく好きで、意図的な演出では絶対に表現できない域だし、聴き手の動物的原始的本能的な芯に突き刺さる魅力があると思っている。\n社会人と大学生が中心のようで、年齢層は高くない。だからこそよい意味で『脂ののった若手』って感じで、習熟度とか積極性とか柔軟性とか冒険心とか余裕とか体力とかいちばんオイシイころあいで、いちばん音楽を自由に好き放題にアツくできる年齢がこのあたりだよなあと、そんなことを思った。\nつづく潤徳女子高等学校さんの演奏は、スマートで隙のない仕上がり。逆にいうとあまり遊びはなくて、演出で表現しようとしていたポップさと演奏とのギャップがちょっと大きくなってしまっていた印象。\n舞台前列に並んだ演出担当の方々で演奏者が隠れてしまって、並んで踊っている方々のために BGM を演奏していますよ、みたいな空気に見えてしまったのもすこし残念だった。そして並んで踊っている方々、一曲まるまる楽器を触れないようだったけれど、あれって本人たちはそれでいいのかしら……？ 吹奏楽界の文化をよく知らないから、あれもふつうのことなのかもしれないけれど。\n最後の東京都職場吹奏楽連盟合同バンドさん。この日のために集まった、東京都内のさまざまな会社の吹奏楽団の有志の方々だそうで。総勢 60 名ほどだとか。\nブルーインパルス、とにかく難しそう……！ ほんの数回の練習でここまで持ってくるのは個々人のレベルの高さがあってこそだなあとは思ったけど、それでもまだ “こなせていない” 感じが否めなかった。シンコペーションのもたもた感とか速いスケールのぐらつきとか、全体的に力業で合わせにいっている印象。でもかっこうよい曲でたのしい。\n民謡は和太鼓がイイ味を出していた。日本の民謡を西洋の楽器と様式で演奏するのって、よくよく考えるとふしぎだけれど、やってみるとけっこう合うのよね。民謡の勢いと吹奏楽の破裂音は相性がよい。\nこれとこの次の二曲は、聴いていてたのしかった。とくに最後のアフリカンシンフォニー、すっと耳に入ってきてきもちよかった。演奏するほうも演奏に慣れていそうで、手放しにふわふわと聴いていられる感覚。余裕って大事。\n吹奏楽のコンクールにもやっぱり行ってみたくなった。とくに社会人団体のをみてみたい。きっとすごいひとたちがたくさんいる。\n","date":"2014-07-13T23:58:57Z","image":"/archives/1462/img/DSC07899.jpg","permalink":"/archives/1462/","title":"吹奏楽祭 2014 マーチ＆ポップス・イン・HIBIYA に行ってきた"},{"content":"7 月 6 日、水曜日。\n宿河原のいつものお店ことポトス ((ぼくが愛してやまない母校こと多摩高校の最寄り駅、宿河原駅の改札を出てすぐの洋食屋さんのこと。平和でおいしいごはんやさんであると同時に、地元のアー ティストのみなさまがよくライブ会場としてつかう場所でもある。ぼくもよく食べに行くし、ギター部の現役さんも OB 勢も重奏やら独奏やらで何度かお世話になっているところ))さんのオープンマイクの日 ((ポトスで月に数回行われる、いろいろな団体（もちろん個人でもよい）が八つくらい集まって、一団体 15 分くらいの枠でパフォーマンスを披露できる日のこと。音楽のひとが多いけれど、それに限らず、朗読とか、お笑いとか、なんでもありの自由な時間))、所属するバンド（？）の Rubinetto ((そのうち紹介できると思うので割愛するけど、簡単に言うと、アルトギターふたつとアコースティックギターひとつとアルトチェンバロギターひとつとバスギターひとつとベースウクレレひとつを使った、結成してまだ半年も経っていないインストゥルメンタルバンド)) として枠をひとつもらって、二曲だけ演奏してきた。\nギターを始めて 12 年目、これまでいろいろなステージに立ってきたつもりだけれど、どれもお客さんはぼくらのギターの音を聴く前提で来ている方々ばかりだった。演奏者とお客さんの間でそういう前提が共有されていると、弾くほうも弾きやすいし、聴くほうも聴きやすい。どちらにとってもよいことだけど、悪くいえば箱庭的温室的内輪感から抜けにくくなっている要因でもあった。\nそんな中で今回、お客さんとしてぼくらの前に座っている数十名の方々のほとんどは、ぼくらが今日ここで演奏することを、ぼくらが弾き出すその瞬間まで知らなかった方々なわけで。何のネームバリュもない知名度ゼロの状態のぼくらが、いってしまえばたまたま同じ時間に同じ空間に居ただけの、わかりやすくいうと別にぼくらの音を聴きにきたわけではないお客さんを前に弾く。\nこれはぼくにとっては未体験の感覚で、なるほどこういう状態で弾くと弾く方も聴く方もこうなるのかと、ものすごく新鮮だった。個人的なミスはてんこもりだったので反省は大きいけれど、やっぱりひとの前で弾くことはどういう形態であれいろいろ見えて糧になるもので。こういうところでも闘えるようにならないといけない。\nたった二曲だけだったけれど、たのしかった。お世辞にもギターの生音に音響的に向いているとは言えないハコではあるけれど、気張らずに演奏できる場所が近くにあることはすごく貴重だし、観客との距離が近くて生々しい反応がもらえるのはうれしいしたのしいしきもちがよい。\n他の出演者の方ともいろいろお話もできたし、知らない曲を紹介してもらえたし、知らない世界も観られたし、ライドシンバルを菜箸で操る謎のおじさんとフルートのおねえさんともセッションもできて、盛りだくさんだった。\n地元の方々に愛される空間は居心地がよい。今度は単独でなにかできたらいいなあと、そんなことを思った。\n","date":"2014-07-13T13:13:22Z","image":"/archives/1436/img/DSC07861.jpg","permalink":"/archives/1436/","title":"宿河原のいつものお店、ポトスのオープンマイクに参加してきた"},{"content":"UnisOno さん主催の、虹晴れギターアンサンブルコンサート。友人知人がぞろぞろと出演するので行ってきた。7 月 6 日、相模女子大学グリーンホールの多目的ホールにて。\n出演団体は全部で四つで、アンサンブルレインボーさん、ギターアンサンブルピュルテさん、ギターアンサンブル木の響さん、そして主催の UnisOno さん。どこもこの近辺を中心に活動を続けている団体で、ピュルテさんは結成して 19 年、レインボーさんはもう 28 年だそうで。みなさん歴史がある。\nどの団体も、団体ごとの嗜好と対象とする聴衆の違いがひじょうに色濃く出た曲目。\n個人的な好みを白状すると、ギターアンサンブルでのポップスってどうあがいてもポップにはなれない気がしていて、実は弾くのも聴くのもだいぶ苦手な分野。とはいえ結局はどこも各々が活動するフィールドに合わせた選曲なわけで、そこでぼくの好み云々を言ってもどうしようもないし、まあたまにはそういう演奏会もあるよね。\n……と思うくらいにはポップスが多いプログラムの中で、クラシック主体で曲を並べてきた UnisOno さんには、だからちょっと期待していたのだけれど、これはこれで何とも、何とも……。\n指揮者が居なくてもこの人数とこの曲目を空中分解させないで最後まで流せるのはこの方々のつよいところではあるとは思うものの、そうはいってもこの人数とこの曲目でこれ以上の演奏を目指すならやっぱり指揮（またはそれと同等の柱になりうるコンサートマスタかコンサートミストレス）はいないとつらいのではと。\nこんみすさんに音を集めようとはしていたようだけれど、その所為で表現にエネルギィを使えていない印象で、高校生のように若さと活力にみちあふれているわけでもなく、かといって OB らしいオトナな上品さにみちているわけでもなく、できあがりはどっちつかずなぼんやりとした音楽。\nそんな中でもガリレオさんの曲は、弾き手のテンションも高かったし、勢いに乗って音もうまく合ってきていたので群を抜いて聴きやすかった。他の曲もこうなればたのしいのになあと。惜しいところ。\n先日のロビィコンサートのときはそこまで違和感はなかったし気軽に聴けていたので、いっそのことあのくらいのカジュアルさを前面に出してくれたほうがぼくには魅力的にうつる。もっとも、あのときは指揮がいたから、その影響もあるのかもしれないけれど。\n団体として何を理想としてどういうモチベーションで活動しているのか、ちょっと気になった演奏だった。あの団体もいろいろな演奏会に出るようになって、結成当初より露出は圧倒的に増えているわけだし、変化は折にふれて追っていきたい。\nそんな感じでもやもやしながら帰った。ひとのふりみてなんとやらとも、よくいったものだなあと思いつつ。\n","date":"2014-07-11T16:02:56Z","image":"/archives/1408/img/DSC07844.jpg","permalink":"/archives/1408/","title":"虹晴れギターアンサンブルコンサートに行ってきた"},{"content":"Android 端末（Xperia A）が修理から戻ってきたからアプリケーションをせこせこと入れなおしていたら、Kindle for Android の本文のフォントが “ゴシック” しか選べなくなっていたことに気が付いた。\nもともと本は紙で読むひとだったので、これまではずっと紙の本に合わせて明朝体で読んでいたのだけれど。\nためしにゴシック体のまま読んでみたら違和感がすごくて気持ち悪い。直したい。\nいろいろ調べてもいろいろ試してもよくわからないので、やむなくサポートセンタに問い合わせ。\nチャット窓口ですぐに解決策が出てくるかと思いきや、詳細調査になって、結局返事が来るまで数日かかったけれど、最終的には直った。\n直し方 カスタマサービスのひとに教えてもらった方法は二つ。\n一つめは、『吾輩は猫である（Kindle 版）』をダウンロードしてみよ、というもの。\nKindle for Android を開いて、右上のショッピングカートボタンをタップ 検索するなどして『吾輩は猫である（Kindle 版）』のページに行き、購入する 端末にダウンロードする 『吾輩は猫である（Kindle 版）』を開く フォントの選択肢を確認する え、これだけ？ と思ってやってみたら案の定直らなかった。\n結局、教えてもらった二つめの方法で直った。英中辞書をダウンロードする方法。\n英語が含まれている Kindle 本を開く どれでもいいので英単語を長押し 表示されるボックスの右上の本のマークをタップ [英語 - 中文] をタップして [ダウンロード] ダウンロード終了後、[全文表示] をタップ 端末の [戻る] ボタンで全文表示を終了 再度、どれでもいいので英単語を長押し 表示されるボックスの右上の本のマークをタップ [英語 - 日本語] をタップ フォントの選択肢を確認する 手順 7 から 9 はたぶん設定をもとに戻すためだけのもので、実質は手順 5 あたりで始まる外字フォントのダウンロードが効いているっぽい。\n解決。\n切り分け 問い合わせる前にこっち側で調べたのは以下。\n別の Android 端末では “明朝” が選べる アプリケーションのバージョンは同一（4.5.1.6） Android のバージョンがちがう（Xperia A は 4.2.2、こっちは 4.1）のでその所為？ もしくは機種に依存？ 特定の本に限らずどの本でも同じ症状である アプリケーションをアンインストールして再度インストールしなおしても変わらない 原因 不明。\n上の方法で直したあと、再現性を確認しようとしていちどアンインストールしてから再度インストールしたら、最初から “明朝” が表示されてしまった。再現性なし、という残念な結果に。直る前は数回入れなおしてもだめなままだったのに……。\nあれかな、バンドルされていた辞書を全部消した状態で端末が修理で初期化されたから必要なコンポーネントがダウンロードされないままだったのかな。修理前の端末では上でいう外字フォントっぽいものが辞書にくっついて記憶のないままにダウンロードされていたのかもしれない。謎。\nたぶんこの状態で端末を初期化したらきっと再現するだろうなあと思いながらも、そこまで深追いする意味もないのでここまで。\n","date":"2014-07-02T16:29:44Z","image":"/archives/1379/img/011.png","permalink":"/archives/1379/","title":"Kindle for Android でフォントに “明朝” が選べなくなったときの対処"},{"content":"6 月 22 日、大宮高校ギター部さんの定期演奏会に行ってきた。\nここももうコンクールの演奏は長いこと聴いてきたけれど、定期演奏会にはまだ行ったことがなくて。いろいろと身の回りでも動きがあったので、行くにはよい頃合いかなあみたいな、そんな動機。\nさびれた市民館での手作り感あふれる演奏会って、ぼくにはとても魅力的。自分の高校の頃を思い出すよね、この年季の入った市民館独特の空気がほんとうになつかしい。みなとみらいとかミューザとかそういうのもそれはそれでいいけど、やっぱりなんていうか、日常の延長で居られる空気の演奏会って大事。\n冒頭の “シェリーに口づけ” をすごく手堅く平和にまとめてきていたので、なるほど今日の演奏会はこの空気なのねーと思っていたら、演奏後の MC がちょうハイテンションで『ハイど～も～☆』と始まって、このギャップが…… ギャップが……！！！\nそうはいっても MC、とくに女性陣がよい空気を作れていた感じ。耳にやさしい自然体。“クラシックギター” というだけで身構えられがちなこの世界では、観客に力を抜いてもらうために MC は地味にだいじなので、その意味ですごくよく作用していたと思う。\nでもだからこそ、白状すると、冒頭で感じた MC のテンションと演奏のテンションのギャップが、最後まで抜けきらなかったのはちょっと残念。\n演奏はどの曲もとても堅実で、きっちりとしたほんとうにていねいなつくり。早いところや複雑なところでは若干の揺れもあったけれど、組み立てがうまいから基本的に不安を感じない演奏。\nでも逆にいうと、いうなればとても優等生的で、すごくさらりとそつなく弾いてしまうので、魅せどころを魅せきらないままさくさくと進んでしまう面もあり。クラスにひとりふたり居る、涼しい顔をしてさらりと何でもうまくこなしてしまうひとのような、すごいと思う反面ちょっとものたりないとも思うような、そんな感覚もあった。このあたり、つくりかたがむずかしいところでもあるのだけれど……。\n個人的にいちばんよかったのは、“人生のメリーゴーランド” の、とくにアルト勢だけで合わせるところ。あの感覚、テンポではなくうたで合わせるあの緊張感と濃く絡み合う空気、とてもよかった。もうすこしうたに合わせた音質になれると文句なしだったけど、それでもじゅうぶんよいうたいかただった。よかった。\nあとはアンコールの一曲目。いちばん力が抜けていて、ラフな空気で気軽に聴けてたのしかった。この曲に限らず、プライムのストロークの安定感と勢いは全体のよい核になっていた感じ。ポップスが多めの演奏会だったからなおのこと。\nあ、あとウクレレもちょうかわいかった。\n“シェリーに口づけ” とか “大フーガ” とか、いろいろなギター合奏団体が昔から弾いている曲は、ここはこう弾くあそこはああ合わせるって、ある種の型が決まっているものが多い。だからその型がわかれば比較的短時間で形になるし、合わせる楽しさも味わいやすいのだけれど、でも言ってしまえば、演奏の “個性” はさらにその一歩先の世界から生まれるものなわけで。\n手堅くまとめる力とか、ていねいかつさらっと弾ける力は、どう考えてもまちがいなく強みだから、その強みを活かした大宮高校ならではの音楽って何だろうって、そういう攻め方もありかもしれないなあと。\n手堅くまとめるのはあのコーチの得意技（？）なので、いっしょにその先の何かをぶちやぶれると世界が広がりそうな気がする。もっと好き放題やっても平気だって、自由であることは許されているって、表現の振れ幅を爆発させる方法はきっとコーチが教えてくれる……！\n個々人のポテンシャルは高そうだし、さてさてそうすると来年に向けてこの方々はどう変わっていくのかしらと。環境の変化は革命を起こすよい機会だし、コーチの手腕に期待ですね。\nまずはコンクール、そしてその先の来年の定期演奏会へ。三ヶ月後と一年後、たのしい演奏をたのしみにしています。\n","date":"2014-06-26T16:42:42Z","image":"/archives/1353/img/DSC07780.jpg","permalink":"/archives/1353/","title":"大宮高校ギター部の定期演奏会で、なつかしい堅実な音を聴いてきた"},{"content":"6 月 21 日。ソニー吹奏楽団さんの定期演奏会に呼ばれたので行ってきた。会場は文京シビックホールの大ホール。1,800 席ほどの大きなハコ。\n去年は同じ日に他のコンサートもかぶっていて、ハシゴしたせいで 30 分くらいしか聴けなかったのだけれど、今年は本プログラムは全部聴けた。よかった。\n先の 全国職場バンドフェスティバル のときは二曲だけだったからあまり意識しなかったけれど、こうやってソニーさんの音だけを二時間聴いていると、すごくまじめで、安定した丁寧な音づくりがされている印象を受ける。\n音のバランス、楽器のまとまり、縦のつながり、リズム感、拍節感、緻密なところ、細かいところにすごくこだわって気が遣われていそうな。個々人のスキルに任せるところもありながらも、それよりはそれを基礎にして全体としてきれいに統率を取ることを是としている、とでもいうか、うまくいえないけどそういうつくり方なのかなあと思った。\nヤマハさんみたいにテンションあげあげで行く系とはちがって、だからソニーさんは言ってしまえば派手さはそこまでないのだけれど、その分純粋に音楽、パフォーマンスよりも音楽、みたいな空気。オーケストラに近いことを吹奏楽でやろうとしている感覚。指揮の川本先生がもともとその方面の方というのも関係があるのかしら、ないのかしら。どうかな。いずれにせよぼくはクラシック系の耳のひとなので、ひじょうに心地よく聴けた。\n一曲目の序曲は演奏者も客席にもすこし堅さがあった（どの演奏会でもオープニングってそういうものよね、曲もパズル感があって合わせるのむずかしそうだったし）けど、二曲目で “わかりやすい” 曲調になって、会場全体の空気がやわらいだ気がした。あのねっとりとした低音のメロディと、その上で踊る高音、きもちがよい。\n川本先生の指揮はクラシックっぽいなあと前に聴いたときも思ったのだけれど、秋山先生の指揮はそれに対してとても吹奏楽。うまくいえないのだけれど、はずみかたというかきざみかたというか、身体の遣い方とか姿勢とか……。話が若干ずれるけれど、指揮って、指揮者ごとの個性はもちろんあるものの、でもさらにその下のレイヤにジャンルごとの特性というものもある。\nアルメニアンダンスは、演奏者の方々のこの曲に対する好意が感じ取れるような明るい空気だった。みんなたのしそう。そして曲の派手な流れと音圧をさらりとかわす指揮がまたかっこうよい。“激しい曲を涼しい顔で弾く演奏者” に感じるかっこうよさが指揮者にもあった。楽団そのものが指揮者にとってのひとつの大きな楽器。ぼくのだいすきなタイプ。\nガーシュインはリズム感がよかった。ガーシュイン特有の変態的（？）なリズムとコードの変化の目まぐるしさ、自分で（ギターで）弾いたときはぼくは最後まで慣れられなかったのだけれど、ソニーさんはさくっとノれていたようで、安心して聴けた。こういう曲をさらっと作ってくるあたり、先日の舞踏会の美女のワルツ感といい、楽団全体のリズム感覚のよさは強みなのかなあとか。\nそして合唱は！ いいですね！ 大序曲！ 吹奏楽と合唱という組み合わせは初めて聴いたのだけれど、合唱が入ってきた時の高ぶる空気がたまらなかった。チラシで “合唱付きで” と書かれているのを見たときは、合唱が吹奏楽にかき消されそうって思っていたけれど、ぜんぜんそんなことはなく。原理的な意味での楽器との帯域と音色の違いもあるだろうけれど、それにしたって合唱は合唱でべらぼうにうまくて、ぜんぜん吹奏楽に負けていなかった。吹奏楽だけでもいろいろな音が出せるとはいえ、そうはいっても人間の声の力ってやっぱりすごいなあなんて当たり前のことを思った。\n音楽のジャンルの違いもさることながら、同じジャンルの中での団体ごとの個性もいろいろあるようで。シエナさんとか東京佼成さんとか、まだ聴けていない有名どころがたくさんあるので、時間をかけていろいろ聴いてみたい。\nこうなると吹奏楽に関していまだにズブの素人のままなのが悔しくなってくるので、編成とか楽器とか吹奏楽の基礎基本がわかったうえでの吹奏楽的な聴き方もしてみたいから、すこしはお勉強でもしてみようと思う。\n","date":"2014-06-24T03:30:15Z","image":"/archives/1342/img/DSC07762.jpg","permalink":"/archives/1342/","title":"ソニー吹奏楽団の定期演奏会で、緻密で丁寧な吹奏楽を聴いてきた"},{"content":"6 月 15 日。藤沢にある新堀学園の本館、その三階のオーケストラスタジオで。\nDANROK さん、ポーランドの音楽のイベントに招待されたようで、今回はそれを記念して現地で弾くのと同じプログラムを先に日本で、という主旨らしい。\n生で聴いたのはもう何回目かわからないけれど、見るたびに勢いが増している印象。昔（昔？）の DANROK さんは身体の遣い方になんとなく演技臭さとか不自然さがあって、見た目の所為で若干素直に聴けないところがあったのだけれど、最近（最近？）はどんどん違和感のない “自然な派手さ” になってきていてたのしい。\nとはいえもちろん、本当に自然な身体の遣い方というよりは、どちらかといえば “演技” ではあるのだろうけど。でも演技しつくされて自然に見えるのか、あるいは演奏者の方々にとってはもはやあれが自然だからそう見えるのか、いずれにせよ演奏に合ったイイ派手さ。\nちょう派手な演奏とちょう派手な見た目の相乗効果で、全体がひじょうにイケているパフォーマンスになっていて、ひらたくいえばすごくかっこういい。はちゃめちゃに個々人が弾きまくって、一見てんでばらばらに好き勝手に暴れているようにしか見えないのだけれど、でもばっちり波が合っていて、がんがんアツくなっていくあの感じ。\n聴いていても観ていても楽しいし、“外” にいるぼくでもそう思えるので、たぶんよいものなのです。\n合奏用ギターを遣う団体やグループって、どうしてもクラシック音楽を核にしているところが多いから、だから DANROK さんみたいなカジュアル志向、観て楽しい聴いて楽しい “堅くない” 世界、派手で激しくてイケイケの曲を中心に据えている団体はぜんぜんない。\nぜんぜんないわりに、冷静に考えると DANROK さんの居るフィールドってギターに馴染みがない層の耳にも受け入れられやすそうで。クラシック音楽より敷居が圧倒的に低い聴きやすさだし、（濃くて暑苦しいけど）堅苦しくないし、もはやインストゥルメンタルバンドだし。\nだからニッチな──だけど潜在的な需要は大きそうな──ところをうまく狙ってきたなあと。そして勢いにのってひとつのジャンルとしてもうしっかり成立しているし、うまいことやるなあと。\nしかしこうなると新堀グループから離れて動いた方がフットワーク軽くなってやりやすいのではとも思うのだけれど、それはそれでいろいろ難しそうだなあとも。\n演奏はやっぱり個々人のスキルのべらぼうな高さが際立つ音で。生音だったから PA にかき乱される こともなくて、とくに Rock of Mozart のトルコ行進曲で田口さんが抜け出てくるところ、あのうたい方と音色はさいこうだった……！\nしかし慢性的にソプラノギターにちょっと聴きとりにくさが。がんがん攻める中低音勢に単音弾きアポヤンドのしかもソプラノギターであそこまで張り合える伊原さんも伊原さんだけど、それでも喰われてる感は否めなかった…… のが惜しいところ。でもきれいな音の出しにくさがはんぱないあの楽器を軽々と扱えるのはさすがだなあと。\nNRM は全体で見た目を合わせてきていたのがちょっと残念。おいしい拍子木部分で本物の拍子木が出てきてしまったので、せっかくならそこもギターにすればいいのにとも。\nでも全体的にどの曲も叩きやらカッティングやらのパーカッシブな音のいれかたはすごく好き。ギターを普通に弾いているだけでは出せないあの空気感はよいよね。例の『にゃー』もやっぱり全然嫌味な感じはなくて、やりきるとやりきれるというかぶっ飛びきってくれるとぶっ飛びきれるんだなーって思った。学生が真似するとどうしても “真似” になっちゃってね、違和感がね。\n休憩なしで 14 曲突っ走る、演奏会でもコンサートでもなく、“ライブ” っていう言葉のほうが似合う空気。よい。とにかくテンションあげてひたすらエネルギィぶつける系のああいう弾き方も、ぼくもしてみたいよね。\n勢いで CD を買ったら存外によいものだったし、また何かにつけて聴きに行きたいところ。もう少しお安くなるとうれしいんだけど……。\n","date":"2014-06-22T16:25:59Z","image":"/archives/1316/img/DSC07748.jpg","permalink":"/archives/1316/","title":"DANROK のポーランド公演記念コンサートで、突っ走るギターを聴いてきた"},{"content":"ついに取り壊しが始まると、そう聴いた。\n新校舎と旧校舎が、同窓会と称して卒業生に開放された 5 月 31 日。多摩高校、もうしばらく行くことはないだろうと思っていたぼくの母校へ、ぼくはもう一度行ってきた。\n3 棟と 4 棟、取り壊しはここからはじまるらしい。\nうろついてみてももうなにも残っていなくて、転がっているのは歴史の残骸で、漂っているのも歴史の残骸で、染み付いているのも歴史の残骸で、そして残骸にはもう未来がない。\n歴史は記憶としてモノそれ自体に宿る。脳にはそれから漏れ出た残滓がこびりついて、それがぼくに思い出させるだけ。モノが消えれば歴史も記憶も消えて、ぼくにとっては残滓がすべてになる。そんな感覚。\nそれでも、時間はあたらしい歴史をつくってくれる。ぼくが何を思おうと何を感じようと何を考えようと、それとはまったく無関係に、彼らは彼らだけで新しい校舎で歴史をつくりながら生きていく。\n好きに生きてほしいとか、楽しんでほしいとか、そう思うことすらもはや傲慢で暴慢で身勝手で、エゴイズムの塊を外野からどう投げつけたとしても、現実、彼らは勝手に生きていく。彼らの人生にぼくの出番はないし、もうあるべきですらない。\n五十年の歴史は五十年かけないと見られない。それでも見たいなら、勝手に五十年生きればいい。\n五十年後の学校沿革には、きっとたった一行、2014 年に校舎を建て替えたと、そう書かれるだけなのだろうけれど、その一行に詰め込まれた歴史の深さは、いまこのときを生きていないと見られないもので。\nぼくは多摩高校が好きだ。だから五十年後、ぼろぼろになったいまの新校舎を楽しそうな目で語る未来の卒業生に、ぼくは会ってみたいと思う。\n","date":"2014-06-01T16:32:29Z","image":"/archives/1289/img/DSC07554.jpg","permalink":"/archives/1289/","title":"多摩高校、校舎にまつわるモノと記憶"},{"content":"巨匠ペペ・ロメロさんが、伝説の銘器トーレスでタレガを弾く。そういう垂涎の企画があったから行ってきた。5 月 20 日、トッパンホール。\n老成円熟した演奏、と思った。とてもしぶい……！ 貫禄というか、正統というか。雑味のないかんじ。\nイマドキのギタリストさんは定番曲でも自分なりのうたいかたで揺らしてくるひとがおおいイメージでいるのだけれど、この方はなんていうか…… そういうイマドキの流行り廃りを気にする世界とは別の世界に生きているような、正しく “枯れた” 時代、作曲者であるタレガさんに近い世界に生きている方なのかなって。\n個性あふれるわけでもないし、派手さなんてぜんぜんないけど、逆にそれだけ純粋で、正統たる風格のあふれる世界。\nトーレス、生で聴くのは初めてだった。タッチの所為か、ばつぐんに音がよいのかといわれればそんなこともないような気もしてしまって、名前が独り歩きしているところは少なからずありそうだなあとも思ったけれど、 でもよく乾いてるのにほんのりやわらかい音がした。\n早い曲、明るい曲よりは、ゆったりとうたう曲のほうが相性がよく思えて、気持ちよく聴けた。すごく雑にいうと、こういう楽器でヨークとか弾いたらいろいろとイケてないダメな感じになるんだと思う。\n冒険はしない、清く正しい演奏で、モダンなクラシックギターって本来はこういうものなんだって思える、そんな楽器と、そんな演奏だった。\n個性あふれる演奏もいいけど、たまにはこういう年季の入ったまっすぐで純粋な音を聴いて、毒を抜きたいとも思った。\n","date":"2014-05-31T16:23:57Z","image":"/archives/1273/img/DSC07424.jpg","permalink":"/archives/1273/","title":"ペペ・ロメロさんのコンサートで、伝説の銘器トーレスの音を聴いてきた"},{"content":"前のエントリ で書いたブリトニー・スピアーズさんのライブの翌日、5 月 7 日、引き続きラスベガスの Sands Expo \u0026amp; Convention Center 内のホールにて。イマジン・ドラゴンズという方々の、某イベント参加者のための特別な、特に名前のない（たぶん……）ライブに行ってきた。\nこの方々、来日もしたことがあるようで、どうやら有名らしいのだけれど、イベントの告知を受け取るまで知らなかった……。ぐぐる以上の予習することもなく、行ってよい立場にあったから行ったという、正しいファンの方々から殺されそうなモチベーション。でも行ったら圧倒された。ちょうたのしかった。行ってよかった。\n会場の都合上、大がかりな舞台装置は無し。ささやかながらスクリーンと照明はあるものの、基本的にはストレートに “音” で勝負せざるを得ない場。そんな場でもものすごく圧倒的で魅力的なパフォーマンスが繰り広げられて、さすがプロだなーというかさすがロックだなーというか、そんなことを考えながらおなかにずんどこ響きまくる驚異的な音圧を感じていた。\n盛り上げ方とか流れとか、U2 を彷彿とさせる曲が多くて、系統は似ているのかなあとは思った。でも打楽器の使い方がぜんぜん違って、ステージ上に並んでいるのは、ふつうのドラムセットのほかに、背丈ほどもある大きな和太鼓、小さい和太鼓、バスドラム、追加のフロアタム。ヴォーカルの方は片手にマイク、片手にマレット、ときにスティック、あるいは両手にマレット。\nこのたくさんの打楽器から繰り広げられる、攻めまくるほどに攻めまくるドラムサウンドがほんとうに気持ちよくて、なぜああも破綻させずにあそこまで圧をかけられるのか不思議だった。鼓膜の限界なんて気にしたくなかった。暴力的な音にもみくちゃにされることが心地よかった。\nとりあえず CD をぽちったのだけれど、やっぱり CD には “現実的な音” しか入っていない。あの場で繰り広げられたような限界を無視した音はやっぱりライブでしか聴けない。次にこの方々が日本に来たら、だから今度は自主的に行きたいと、そう思えてうれしかった。\n","date":"2014-05-24T23:32:34Z","image":"/archives/1233/img/DSC05510.jpg","permalink":"/archives/1233/","title":"イマジン・ドラゴンズのライブで、ドラムサウンドに圧倒されてきた"},{"content":"だいぶ前の話になるけど、5 月 6 日、火曜日のこと。ブリトニー・スピアーズさんの定期公演、Peace of Me に行ってきた。\n会場はラスベガスの The Axis。Planet Hollywood というホテルの中にあるホール。もともとキャパシティが 7,000 くらいのすごく大きいところだったらしいんだけど、全部で 80 回くらい行われるこの定期公演のため（！）に 360 度の映像投影ができる環境がつくられて、その影響で席が 4,600 まで減ったとのこと。それでも 4,600 てすごいよね。\n初めて知ったんだけど、世の中には “Residency Show” という公演形態があるようで、これは一定の期間内に同じ題目で同じ場所で公演を何回も開くものらしい。期間が決まってて同じ題目で何回も開催という意味では、いわゆる “ツアー” ととても似ているけれど、会場が全部同じ、というところがツアーとは違う、みたいなやつ。\n正直なところこの公演は『連れて行かれた』というのが事実で、そもそも行くこと自体を当日まで──正確にいえば開演の数時間前まで──知らされていなかったし、そしてぼくはブリトニーさんのことをあまりにも知らない。\nとはいえ音楽系のイベントならだいたいは楽しめるので、今から行くよといわれた瞬間にはうれしかったことを覚えている。\n実際、派手な舞台装置と派手な映像と派手な衣装がうまいこと組み合わさってて視界全体でたのしめたり、バックダンサのキレがすてきでかっこうよかったり、ホールが “Powered by MONSTER” なだけあってちょうよい大爆音だったり、いろいろおもしろかった。\n最近は大規模な映像投影も当たり前になってきたみたいで、物理的な舞台装置の圧倒的な現実感と迫力を、作り込まれた映像の抽象感が思いっきり引き上げてる感があった。そこにそれ専用の衣装をきた方々のダンスが加わって、そうやって世界がうまいこと作られていた。そして曲ごとにまるっきり世界は変わる。\n舞台装置には舞台装置にしかできないことがあるし、映像表現には映像表現にしかできないことがあるし、身体表現には身体表現にしかできないことがある。制作コストと演出効果の関係とか予算の経年変化とかにも考えが及びかけたけど、何にせよ『良いとこ取り』できていた感があって。\n最近とんとポピュラ音楽のライブには行かなくなっていたので、ひさしぶりの感覚だった。たのしかった。\n調べると『口パク公演』とか言われているらしいけれど、この手のライブでそれを言っても誰も得しないよなあと。演奏会ではなくて “公演” なわけで、音楽だけでなくて演出含めてできあがっているものなわけで、ある意味で『総合芸術』的なものなわけで。売り物は音楽ではなく場であり空気であり体験そのものなので、そんな感じで味わえればよいと思った。\n全身にずんどこひびく爆音は大好物なので、こういう音もたまには浴びに行きたい。\n","date":"2014-05-19T00:07:11Z","image":"/archives/1227/img/DSC05177.jpg","permalink":"/archives/1227/","title":"ブリトニー・スピアーズさんの定期公演、Peace of Me に行ってきた"},{"content":"4 月 29 日、日曜日。須川展也さんのデビュー 30 周年記念コンサートに行ってきた。会場は東京文化会館、もちろん大ホール。\n先日ふらりと観に行った 全国職場バンドフェスティバル で須川さん自身が宣伝されて、その日のうちにチケットを手配したこのコンサート。楽しみにしていたその期待以上にとても楽しいコンサートだった。\nコンサートは二部構成で、第一部は須川さんとクラシックギター ((まさかの鈴木大介さん！))、須川さんとピアノ ((小柳美奈子さん))、須川さん含めたサクソフォン四重奏 ((トルヴェール・クヮルテットさん))とピアノ ((小柳美奈子さん))、という各構成での演奏。第二部はヤマハ吹奏楽団さん ((あいかわらずイケてるパーカッショニストさんが叩くタンバリンがさいこうだった))を迎えて、須川さんが指揮だったりソロだったりでの演奏 ((プログラム上は “吹き振り” がある予定だったけどなくなったようで、須川さんがソロの曲はすべて山下一史さんがタクトを持っていた))。全部で三時間ちかく、いろいろな演奏者、いろいろな編成、いろいろな曲、盛りだくさん。\nサクソフォンから三味線の音がしたときはほんとうに衝撃だった。そしてサクソフォンでもグリッサンド（ポルタメント？）ができることを初めて知った。音階を持つ（音階に縛られる）楽器とばかり。\n全体的に、技術面でも音色面でも、サクソフォンであんな音出せるのか、という驚きの連続。何をどう吹けばああいう音になるのかさっぱり分からない。以前のエントリ でも書いた通り、吹奏楽に関しては相変わらずほんとうにド素人なので、専門用語は何もわからないけれど、素人目にも “普通ではない” ことは見て取れたし、きっと特殊奏法が山盛り詰まってたんだと思う。\n多彩な表情があって、すばらしい世界だった。余裕のあるさいこうの熱演、とでもいうか、30 年というキャリアが培った音を生で聴けてほんとうによかった。耳を通じて脳に自然としみ込む心地よい時間。\n須川さん、6 月にシエナさんとも共演されるようで、行きたいけど、土曜日……。\n","date":"2014-04-29T14:09:56Z","image":"/archives/1208/img/DSC04202.jpg","permalink":"/archives/1208/","title":"須川展也さんのデビュー 30 周年記念コンサートで、変幻自在のサクソフォンを聴いてきた"},{"content":"先日の日曜日、4 月 20 日。\nOB でもないのになにかと個人的に縁が多すぎるほどに多い、相模原中等教育学校クラシックギター部さんの定期演奏会。\n相模大野高校から相模原中等教育学校に変わって六年目。中等学校の一期生が最上級生となる今年、初めて “中等生” のみによって開催される記念すべき演奏会。回数こそ高校のそれを受け継いで “第 26 回” と銘打ってはいるものの、その意味はほとんど “第 1 回” に等しい。\nそんな演奏会、想像以上の演奏が聴けることを楽しみにしていたら、想像以上すぎるほどに想像以上すぎた。奇跡と思うことも厭わない、何の躊躇いもなくぼくはこれを手放しに賞賛できる、ぼくが聴いたのはそんな音だった。\nぼくがギターアンサンブルの世界に触れてからまだ十数年しか経っていないけれど、それでもそこそこの数の部活の演奏を聴いてきたし、それと同時にたくさんの『お客さん』にも会ってきた。\nこの世界にかぎらずどの分野のどの演奏会でも、“部活” の演奏会であれば、一般のお客さんがよく言うのは、『かっこうよかった』『うまかった』『すごかった』『きれいだった』というだいたいが至極まっとうで前向きな評価。でもそれはまた同時に、だいたいが『まだ中学生なのに』とか『高校生にしては』とか、そういう暗黙のうちに共有される接頭辞があってのもので、平たく言えばそもそもの評価基準が低い。だからこそ当の学生からすれば、そういう色眼鏡を通さない “まっとうな評価” を得るのはひどく難しいことでもある。\n純粋に、音楽としてどうなのか。ギターとしてどうなのか。ギター合奏としてどうなのか。\nとはいうものの、組織のアイデンティティとして “部活” という絶対の事実はあるわけで、であれば “たのしいは正義” という側面があるのも事実なわけで、自分の中の評価軸をどこに持つか、どう前提を置くか、どういう姿勢で聴くか、演奏を聴くときはいつも悩ましいのだけれど、今回のこの演奏会は、いつも以上に部活だとか学生だとかそういう能書きのどうでもよすぎるどうでもよさが強烈に身に染みたものだった。\nもちろん曲によって出来はばらばらではあったけれど、それでもあの演奏が “学生” である彼らによってつくられた現実を、ぼくは認めたくなかった。だからこそぼくは、あのさいこうの演奏が、ぼくの “友人たち” によってつくられた事実を全力で肯定したいし、心から歓迎して受け入れたい。そう思えることが、ぼくはたまらなくうれしい。\nほんとうに、よいものを観た。\n指揮は言うまでもなく当たり前のようにぶっとんでいる。指揮だけぶっとんでいるのとか、演奏に指揮が追いついていないとか、そんなものはそこらじゅうに転がっているのだけれど、でも演奏があの指揮にがっつり食いついていけている世界が、こんな近くにあったというのは予想外。冗談抜きで奇跡的な完成度。\nJGA 界隈の学生指揮者というと、指導陣のクセがうつって “JAEM 風” っぽく見えることがよくあるのだけれど、あの指揮者はもう自分のスタイルを確立して、完全に独立しているように見える。演奏者の食らいつきを余裕で受け流す瞬間、受け入れる瞬間。圧をかける瞬間。抜く瞬間。刺す瞬間、歩く瞬間、踊る瞬間。お互いにわかりきっているからこそできる、ほんとうにリアルタイムの、あの場のあの瞬間でしか作り得ない、一瞬の視線と音と、空気と呼吸だけで信頼できる仲間と意思を伝え合うやりとりのおもしろさ、たのしさ。そんな世界を全身で味わいつくそうとしている筋肉の動きが見える背中は、客席から見ているとほんとうにかっこういい。\nそしてそういう指揮者に身をゆだねて、ギターを意のままに爆発させる、演奏者側の一期生。うすっぺらい感動物語とかみせかけばかりのうさんくさい絆とかいう概念があふれるこの時代に、ほんとうにきれいで純粋な信頼関係を見られた気がした。\n至高だったのは、そんな一期生十人によるベートーヴェンのセリオーソ。指揮はなくて、全員が演奏者。自由に、ほんとうに自由に音楽をつくっていて、この演奏会でいちばんこれが好きだった。何にも縛られずに、その場の空気に全身を預けて、流れにのって弾く。お互いもはや目を合わせる必要もなさそうな、目をつぶっていても完璧に意思の疎通がはかれそうな、そんな濃密な空気。\n色のまったくちがう細かな断片が散りばめられたあの曲を、よくもまああそこまで分析して解釈して、そしてその通りに表現しきってくれたものだと。厳しい音を弾ききった次の瞬間、まったく違う表情で踊り出てくるなめらかなアルトギターの音。なかなか聴けるものではない。数字だけ考えればパート間の人数比もあまりよくはなかったのだけれど、彼らにとってはこれこそが最高の比率のようで、実際、一分の隙もなく緻密に組み立てられていた印象。\nほんとうに好きで、ほんとうにたのしくて、ほんとうにこれがやりたくて、だからこそいまあそこに居て、いまああいう音づくりができているのだと。そう思わざるを得ない、おそろしくきれいな世界。\nセリオーソだけでなくて、バルトークも至高だった。コンクール曲だけあって、はっきりと完成されている感がある。コンクールのときもそうだったけれど、コンクール以上に冒頭部で瞬時に惹きつけられたまま、ほんとうにあっという間に終わってしまった。\nアラジンはちょうばかっぽい（ほめてる）演出とはうらはらに、しっかり丁寧にきれいだったのでよい意味ですごく裏切られたし、一年前は小学生だった二年生がタッチで身体をがんがんつかえていて衝撃だったし ((よい先輩のよい生き方をみて育ったからかしらね、やっぱり))。Surge III は、意図しているのかいないのかわからないけれど荒っぽさがすごく Surge っぽくて好きだったし ((多摩のより好きだった))、Surge V はコンクールのときよりあっさりしていて、曲調と相まってさわやかさがよく表れていたし ((竹内先生らしからぬさわやかさなので、もともとこってりねちねち弾くべきものではないって思ってる))。\n反面、闘牛士が若干曲に負け気味で理想と現実が噛み合ってなかったりとか ((理想が先走って、現実が追い付けていない感じ))、指揮がちょっと猫背だったりとか ((肩を張って顎を引いたらよくなりそう))。車輪の歌がちょっとぐちゃぐちゃ感あったりとか ((これはちょっと残念だった…… メロディが聴こえない。ポップスよりクラシック曲のほうが得意そう))。アレグロがちょっときれいすぎて逆にもやっとしていたりとか ((あのテンポで回っていたのは衝撃だった。鋭い音と甘い音を使い分けて、はっきりきっぱりしたコントラストが出せるようになるともっとよさそう。個人的には原譜で sul G の指定があるところの sul G っぽさにはぜひこだわってほしいところ。もちろん、コンクールをめがけた曲だから、今の時期のこの段階であれこれいうべきものではないのだけれど))。弾く人数が少ないところではそもそも音量がホールに負けている感が否めなかったりとか ((前の方の席だったからよかったけど、後ろまで届いていたか怪しい気が。ソロとかとくに……。技術的な意味で楽器をもっとよく鳴らせば音量も稼げそうではある))。\nいろいろあったけれど、総じて、とんでもない演奏会だったと思う。極端に言えば、“学生のギターアンサンブル” の奇跡的な完成形めいたものが見えた気がした、そんな演奏会だった。\nとはいえ、こうなると来年以降がものすごく、ほんとうにものすごく大変そう。\n一期生は、一期生だからこそ、五年間ずっと先頭に立っていた、というか、立たざるを得なかったはず。“先輩” が居ないから、自分たちの前には誰もいなくて、すべてを自分たちで作らなければならなかったし、自分たちが先陣を切らなければならない責務があったはず。\n一期生には圧倒的なカリスマ性がある。これはもうおそらく事実で、でもそれはもって生まれた部分以上に、五年間かけて培われた部分がひじょうに大きいと思っている。SSSCGC の創造主たる “神” 、あるいは “伝説” の一期生を、二期生は追いつくだけでなく追いこさないといけない。\n一期生の部活に対する方法論は、きっとあの十人だからできたものだし、十人という人数だからこそのものでもありそう。五年間ずっと先頭に立っていた一期生と、四年間ずっと自分たちをひっぱってくれるひとがいた二期生のギャップは、たぶん想像以上に大きい。二期生は二期生で、じぶんたちの方法を模索して、確立しないといけない。\n組織は、創設した代が抜けてからが本番。きっとおそろしく大変だけれど、それ以上におそろしくたのしい世界だから、やりたいようにやって、こらえながらも余裕をもってくぐりぬけてほしいと思う。彼らがコンクールまでどう生きて、定期演奏会までどう生きるのか、新しい伝説になることを、全力で期待したい。\n彼らと関われて、ほんとうによかった。この演奏会を作り上げてくれたことが、ひとりの友人として、ぼくはとても誇らしい。\nそしていつか、彼らと同じステージに立てたらいいなあと、そんなことを思っている。\n","date":"2014-04-23T14:00:44Z","image":"/archives/1184/img/DSC03806.jpg","permalink":"/archives/1184/","title":"相模原中等教育学校クラシックギター部の定期演奏会で、とんでもなく奇跡的な演奏を聴いてきた"},{"content":"ボサノヴァのボの字も知らないぼくが、いろいろと縁があって、ブラジル音楽のライブとワークショップに参加できることになった。4 月 11 日、とくに予定のない夜になるはずだった金曜日のお話。\nたまたま、ほんとうにたまたま、友人との雑談のなかで話題にぽっと出てきた今回の件。自分がまったく知らない分野の音楽に触れることのおもしろさは 先日の吹奏楽 でも存分に味わっていたので、二つ返事で参加することに。\n会場は、ブラジル音楽界隈では有名らしい MADEIRA。普段は展示場を兼ねているようで、オフィスの 1 階とは思えないステキ空間。天井が高くてガラス張りのフロアに、おおきなヤシの木みたいな観葉植物と JBL のスピーカが共生していた。壁にはこじゃれた絵、あちらこちらに飾ってある紙でできたかわいい小物。その横にひっそりと見慣れた波動スピーカ。\nカフェとバーとライブハウスを足して 3 で割ったような、こういうところで好きな音楽をつまびきながらのんびりと語り合えたらさいこうだろうなあと思える、とても居心地のよい場所だった。そして実際そういう時間のためにこの場があるようで、なんというか、あるところにはあるんだなあと。\nキャパシティは 50 人ほどで、お客さん同士はお互い知っている顔がおおかったもよう。あちらこちらであいさつ合戦が繰り広げられていた。\n『MADEIRA 5 周年記念特別企画 / ジョアン・リラ来日記念スペシャルライブ \u0026amp; ワークショップ』と名付けられた今回のイベント。“ワークショップ” という名の通り、前半は実際の演奏で例示しながらブラジル音楽のさまざまリズムを解説していくワークショップ形式。ギターとヴォーカルがジョアン・リラさん。隣に通訳を兼ねたパーカッションの方。\nブラジル音楽ってサンバとショーロとボサノヴァでしょ、みたいな、ぼくはさいこうにひどくて雑な理解しかしていなかったのだけれど、よもやここまで多彩なリズムがあるとはまったく想像もしていなかった。たのしい。\nジョアン・リラさんが『次は○○のリズムで～』と次々に解説を交えつつ披露してくれるのだけれど、通訳の方の発するカタカナの専門用語が、ぼくの知らない単語すぎてまず聴き取れない。ポルトガル語だってこともあって、そもそもカタカナ化することが難しくもあるのだけれど、それにしてもだばだば未知の用語があふれるように出てきて、しかもぼく以外のお客さんはみんなふんふんと頷きながら聴いている。ブラジル音楽の世界ではある意味で基礎的なところなのかもしれないけれど、白状すると全然わからない。\nざっくり振り返るだけでも、パルチード・アウト、サンバ・ヂ・ハイース、サンバ・ジャズ、サンバ・カンソン、ショーロ、ショーロ・ロマンチコ（サンバ・ロマンチコ？）、ショッチ、マラカトゥ、バイヨン、シランダ、コーコ、などなど。メモしきれていないのもたくさんあった。そういうリズム、ジャンルを、お手本つきで解説してくれる。\nこのイベントに来る前から、奥が深そうだということは予想してはいた。でもすごく原始的なものだから、だからこそこんなに細かく分類されているなんて思ってもいなかった。まったく逆だった。\nこれは低音の流れがすごく大事で、とか。これは輪になって踊るときのだから一拍目に必ず強烈なアクセントが、とか。最近のこれはすごく早いけれど昔はゆっくりだったんだ、とか。これはギターにアクセントはなくて、全部同じ音量で、とか。これはセッションしやすいように、ギターは複雑にしないで、和声もシンプルに、とか。その代わりこれはうたの位置が複雑で、シンコペーションでわざとずらすんだ、とか。北東部からはたくさんのリズムが生まれて、それはたくさんの国からの移民が居たからだ、とか。これはレゲエとルーツがいっしょなんだ、とか。これとこれは楽譜に書くとまったくいっしょなんだけど、演奏するとこんなに違うんだ、とか。\n解説だけでなく“ダメな演奏” をわざと挟んでくれることもあって、本来の演奏と聴き比べるとたしかに圧倒的にノれることがよくわかる。日本でよく聴くボサノヴァのリズムはここがおかしくて、ほんとうはこうするんだ、こうだとすごくよいでしょ、とか。\n繰り広げられたのは、ぼくが普段よく関わっているいわゆるクラシック音楽の緻密極まりない音作りとはまったく違う世界。もっと人間本来の、根源的、あるいは原始的なところに響くリズム。西洋音楽は “計算” されているけれど、ブラジル音楽はそういう狡猾さはまったくなくて、もっとこう、人間の本能というか魂というか、“欲” に素直に従っている気がして、頭をからっぽにしてふわふわと聴いていたくなる心地よさがあった。\nよいものですね。よいものです。たのしいし、きもちがよい。音の流れに身を任せられる感覚、久しぶり。\n途中、クラリネットも入って三人になるシーンもあった。客席ですぐ横にいたひとがいきなり呼ばれて戸惑いながらも舞台に上がって初見で吹く、というのも、こういうアットホーム感のある会場ならではなんだろうなあと。ぼくらにはない空気なのでとても新鮮。初見なのに合わせどころばっちりだし。こういう音楽って、どこまで楽譜に書いてあるのかしら？ どこからがアドリビタム？ ジャズにも思うことだけれど、その場で一回限りしか聴けない音って、すごく貴重で崇高なもの。\nワークショップ中に例示された中には、アントニオ・カルロス・ジョビンさんとか、バーデン・パウエルさんとか、知っている名前もちらほらあって、ギターの独奏ではメジャな曲でも、ただしいリズム感を出そうとしたら、この深遠な世界に触れざるを得なくなるんだろうなあとか、そんな怖いもの見たさも。\n休憩を挟んだ後半はライブ。女性のヴォーカリストさん兼ギタリストさんが加わって、三人でのセッション。これもよいものだった。ジョアン・リラさんの男声もグルーヴィですてきだったけれど、女声のゆったり感、浮遊感もきもちがよかった。\nそんなこんなで、全然知らない世界に触れて刺激をうけまくった夜。似非クラシック畑の人間が異世界に飛びこんだらこうなった。詳しいことは全然わからないけれど、でもちょうすごかったしさいこうにイケてた。\nこれ、たたでさえおもしろかったけれど、前提知識があったら絶対にもっとおもしろい。なんでもそうだけれど、お勉強って基礎は地味でも、基礎から一歩先に進んだ瞬間に圧倒的に世界が広がっておもしろくなるよね。\nギターアンサンブルを知らない方々がぼくらのコンサートに初めて来た時の気分も、もしかしたらこんな感じなんだろうなあと思えたのもひとつの収穫。今回のぼくははっきりと “一見さん” だったわけで、それでもひたすら純粋に楽しめた。ぼくらのコンサートに “一見さん” が来たときに、『詳しいことはよくわかんないけど、でもなんかちょーすごかった！』って、そんな感想がもらえるような演奏ができたらよいなあって。\n終演後、任意参加のセッションもあって ((イベントの Web には “楽器持参でどうぞ！” などと書いてあった))、それも楽しそうだった。ブラジル音楽のセッションだと知らない世界すぎて混ざりようがないので辞去したけど、ああいうところに飛び込んで混ざって弾けたらさいこうにたのしそうでうらやましい。\n自分の音楽スキルはどうしたってほとんどが付け焼刃なので、がっちり依拠できる核が欲しいって、こういうの観ていると思っちゃう。趣味を趣味以上にしたいなら、やっぱりお勉強はだいじ。\nそんなこんなで、いろいろな方にごあいさつもできて、またひとつコミュニティは広がったすてきな夜だった。機会があれば、またどこかで。\n","date":"2014-04-13T06:15:52Z","image":"/archives/1160/img/DSC03708.jpg","permalink":"/archives/1160/","title":"ジョアン・リラさんのライブとワークショップで、ホンモノのボサノヴァに触れてきた"},{"content":"多摩高校ギターアンサンブル部。誰が何と言おうと、何年経とうとぼくのギター活動の原点はここにある。\n早いものでかれこれ付き合いも 12 年目。そんな部活の、第 48 回定期演奏会。4 月 5 日、土曜日のお話。これまでの定期演奏会ではほとんど裏にいたぼくだけれど、今回は 8 年ぶりくらいに客席から観賞。\nしかしほんとうに、なんというか…… 大きくなったなあと。\nただのオンボロの県立高校の、それもただの部活の、65 人もいる部員全員が、自分だけのギターを持って、日本有数のホールに、1,000 人の観客を相手に堂々と立って、そこで音楽をつくっている事実。もはや当たり前になりつつあって麻痺しているけれど、はっきり言えば、常識的に考えてあり得ない、異常なことだと思う。それでも 3 時間の演奏会を走り切れてしまうのが、彼らのもつエネルギィのすごさなのだろうけれど。\nおどろいたのが、学生指揮者の彼の驚異的な伸びっぷり。夏以来とんと観ていなかったのだけれど、自分の動きの正しさに不安を感じているかのような当時の姿勢はどこへやら。ある程度決められた動きの中でも、自分の表現欲が存分に身体に出ていたように見えて、清々しかった。\n重奏も、とくに編曲、やればできるんだからこれまでももっとやればよかったのにと思えるレベル。編曲だけでなくて演奏もよいバランスで、とくにパートリーダさんたちのは完成度が高かった印象。パートリーダさんだけという編成は、低音勢が多すぎてそもそもバランスが悪いという根本的な問題を抱えているのだけれど、それを感じさせない丁寧な仕上がり。編曲もよかったのかな。\n一年生の学年演奏も、この時期でこの仕上がりは想像以上。すごく丁寧だった。最終的にはもっと荒々しくうねりまくってくれるとよいと思うけれど、いまこの段階では、あとで自由に走り回れるだけの堅い基盤づくり、広い土地づくりをきっちり丁寧に進めるほうが大事。そういう意味で、コンクールに向けた途中経過としてはさいこうの状態だったと思えた。\nとはいえどの曲も、このホールでこの演奏となると、ちょっと背伸びしている感も透けて見えてしまって。これだけの多い人数をうまくコントロールできていることは恐るべき統率力ではあるのだけれど、曲作り、表現の面では無難なところで落ち着けたなあというか、落ち着けざるを得なかったのかしらというか、そんな印象は否めない。\nそうした中でもいろいろと、変わろうとした気配が感じられた点はうれしい。例えば妙なぴょこぴょこした動きがなくなってきたこと。例えば重奏の編曲を演奏者自身だけで仕上げるようになってきたこと。行動原理を他人の評価から自分の欲へとうつしつつあるようなもので、部活ってもともとそのほうが健全だよなあと思いながら観ていた。自由にしていいといわれて逆にどうすればいいのかわからない、みたいな迷いがまだあったのかもしれないけれど、手探りで動き出してみたその方向はたぶん間違っていないので、恐れずにこのままがんがん突き進んでほしいと思う。\nなにかの “形” が先代から何の解説もなく遺されていたとしても、もともとその形が生まれたことには必ず意味がある。その意味を考えて、それが今のじぶんたちに本当に必要なのかを考えて、必要だったら取り込めばいいし、必要でなければ捨てればいい。文化とか伝統とかセンパイとか、いろいろと『なんとなく敬意を表して尊重しなければければならなそうなもの』って世の中にはたくさんあるけれど、その実、今の自分にとってそれが必ずしも合理的な最適解かどうかといわれたら、だいたいそんなことはなくて、ほとんどが不合理だし理不尽だし意味不明だし、その程度のものでしかない。\n背伸びも大事だけど、身の丈に合っていることも大事。イキオイだけで登れる高さには限界がある。今までどうだったかはひとまず置いておいて、今の自分にとっていちばんふさわしいのはどれなのか、 地に足をつけて、先を見据えて、一歩一歩踏み固めながら確実に歩を進めるのも、イキオイ以上の最上の価値を生み出すひとつの方法論。\n思うに、前回、56 期の定期演奏会が、それまでの十年近くの流れの “完成系” であり、終着点であり、ひとつの時代の “理想的な終焉” だった。そして 57 期は、その血を継ぎついでいながらもたぶんまったく新しい流れの始まりの代で、きっと進化や発展のきっかけになる代であるように思う。\n変化の片鱗はこの演奏会でも観てとれて、だからぼくはさらにこの血を受け継いだ 58 期が、57 期のつくった変化のきっかけを糧に、これからぼくの知らないあたらしいギターアンサンブル部をつくりあげてくれる気がしていて、それをすごく楽しみにしている。\nこの部活にぼくはぼくなりの正義をもって接してきたし、ぼくなりの哲学をもって接してきたけれど、そうした結果、身の回りの変化とともにそろそろ引こうとこっそり思ったのが去年の話。だから今年は、ぼくの知っていること持っていることを、知るべきひと持つべきひとに渡す、そんな自分なりの移行期間だった。\nOB さんにとっては、これから起きる変化で部活が “自分の常識と違う” 世界になっていくかもしれないけれど、その変化は全力で受け入れて、現役さんが進みたがっている方向に進めるような、お手伝いするならそういうものを、変化に沿った力添えを。尊重すべきであり尊重されるべき意向は、OB たる自分のものではなく、現役さんのそれ。\nそこで求められる “お手伝い” は、もしかしたら “なにも手を出さないこと” かもしれないけれど、もしそうなったらそれはそれで、それはやっぱり現実。部活は現役さんのもので、演奏会は現役さんのもので、時間は現役さんのもので、なにもかも現役さんのもので、それはやっぱり大原則で、部活なんてなにひとつ OB のものではないのだと、部活における “神” はどこまでも現役さんなのだと、ぼくはそういう認識でいる。時間は巻き戻すものではなく、進めるもので、過去は過去、今は今。\nぼくのこういう考え方も、ぼくにとってのぼくだけの正義であって、ぼくはぼくの知っていることしか知らないし、ぼくはぼくの考えられることしか考えられないから、何が正しいかとか、この先どうなるかなんて、なにもわからないし知らないのだけれど。\nそれでもひとりのお客さんとして、ぼくの愛してやまないこの部活が、新時代をどう生きてどう進むのか。大いに期待できそうで、とても楽しみ。\n5 年後 10 年後、そのときぼくはどこでなにをしているのか想像もつかないけれど、人生のすべてをかけて人生の楽しさを心の底から味わいつくしているような、そんな現役さんの演奏会をまた観にいけたらいいなあと思うのでした。\n","date":"2014-04-08T13:20:06Z","image":"/archives/1123/img/DSC036021.jpg","permalink":"/archives/1123/","title":"多摩高校ギターアンサンブル部の定期演奏会で、自分の原点に触れてきた"},{"content":"3 月 30 日、日曜日。山梨県立北杜高校さんのギター部のスプリングコンサートに行ってきた。\n初めて行った一年前につづいて、今回で二回目。去年はちまっと 4 人での参加だったけど、今回はちょっと幅をひろげて、ぼく以外に、中学生ひとり、高校生ふたり、大学生ひとり、浪人生ひとり、社会人ひとりの、全部で 7 人。ステップワゴンを借りて、みんなでわいわい遠足なノリで。\n朝 9 時に集まって、車に乗り込んで、わちゃわちゃしながら 2 時間の道程。調布から中央自動車道を下って、甲府昭和で降りる。去年と同じお店で去年と同じようにみんなでほうとうととりもつ煮をたべたあと、さらに 30 分ほど車で進んで会場の北杜市オオムラサキセンターへ。集合がはやすぎたかなあとも思ったけど、いつも以上にのんびり運転してお昼をのんびりとったので、結果的にはちょうどよかった。\n地域とともに在ること、そして地元の住民の方々に愛されていること、そういうことをひしひしと感じられるこのあたたかな空気の会場に、ああまた来られたなあよかったなあと、二回目にしてこうもうれしく思ってしまうのは、たぶん同行の友人と同じように、ぼくももはやただのファンなんだろうなあと。座布団に座ってギターをのんびりと聴く時間はとても贅沢。よいものです。\nベタ褒め気味に入ったけれど、実際その実力は折り紙つき。ここ何年も惜しくも最優秀賞を逃していた全国学校ギター合奏コンクールで、前回ついに日本一に輝いたこともあり。去年よりもお客さんが多かったような気もした。\n北杜さん、人前で演奏する機会が、他校のギター部よりもおそらく圧倒的に多い。月に数回以上、それこそ毎週のようにどこかでなにかを弾いている印象がある。\nそういう経験が多いのはたぶん北杜さんの圧倒的な武器だし強みだし、演奏からもその “慣れ” が見てわかるくらいには、実際すごくよく作用している。緊張していないわけではないのだけれど、固くなりすぎることもなく、いつもどおりの柔らかい身体で音を作れていそうな、そんな感触がある。\n今回の演奏会では、低音勢の安定感がきわだっていた。すこしやんちゃで主張しすぎている感もあったけれど、不動の基盤があることで、上は上で安心してのびのびとうたえて音をつくれるわけで、とてもきもちがよさそう。重奏のバランス感覚も、さいこうによかった。とくに二団体目の、それも前半。あとで話を聴いたら重奏の対外的なお披露目はこの日が初めてだったそうで。基礎力の違いなのか何なのか。\n一般的に、アルトギターは音質にとくべつに気を遣わないとカシャカシャしてきたない音が出る。よくある学生のアンサンブルは、カシャカシャを大人数で弾いて噪音を楽音で打ち消して『なんとなくきれいっぽくきこえる』というところで終わってしまうのだけれど、対して北杜さんのアルトギターは相変わらずぽろんぽろんときれいに丸い音で、ストレスなく耳に届く。\n数にごまかされているのかと思いきや、重奏やソロなどひとりふたりで弾いているところも出音がきれいなので、個人差はあるものの根本的にきれいっぽい。\nフォームも手の使い方もきれいで、アルペジオでも右手の安定感がくずれないのが学生らしからぬクオリティ。重奏三団体目でアルトのセカンドを弾いていた彼（顧問の先生のお見送りのときにアルトを担当していた彼）とか。たまたま目に入っただけかもしれないけど、ああいう奏者が弊団体に欲しいなあと。\nそして演歌！ 初めて聴いたけどおもしろい。津軽海峡冬景色、爆音の低音勢がうまい『荒さ』になってよい演歌っぽさ。ただその反面、プライムより上が『きれいすぎ』てしまって、演歌にしてはお上品すぎる気もした。メロディを生かしてギターならではの曲として作るのか、こぶしの効いたもともとの演歌らしさを求めるのか、方向付けがむずかしそうだなあと。しかしつきつめたら新しい世界に行けそう。よい編曲だったのでぜひいろいろなところで弾いてほしいところ。\n最後、A 先生が『生徒主体で、やりたい曲を、やりたい編成でやる』とお話していたけれど、実際その通りの理想的な活動ができていそうで、うらやましいかぎり。\nさて、演奏とは関係ないところで、今回初めてわかったのは、中学生高校生を遠方に連れて行こうとするには、意外と『保護者の許可』という壁が高いということ。\n保護者の許可がおりない事例は、本人の意思と時間の余裕とはまったく無関係に発生するうえに、ぼくは保護者と直接話すわけにもいかない（話せても意味がない）のでつらいところ。 保護者がどのような理由で許可しなかったのかを聴けていないので、杞憂かもしれないけれど。\nもっとも、保護者側からすれば、中学生の息子や娘が十以上も歳の離れた謎の社会人の車に何時間も乗って遠くに遊びに行くなんて、ふつうに考えたらそれは心配だろうし、弁解しようがないのも事実。こういうときに『講師』っていう肩書きは有効なんだなあって、ちょっと思った。ぼくは講師ではないので、もちろんそれは使えないけれど。\n北杜市、ぼくら OB 勢は車が動かせるし定期演奏会の練習もないし金銭的な余裕もあるので時間さえゆるせば気軽に行けるところではあるけれど、本当はぼくはぼくらを起点にして、OB 勢よりも現役同士の交流につなげたいとものすごく思っている。\nしかし物理的な距離はいかんともしがたいのが現実で、中高生は車は運転できないし電車で行くにしても金銭的な負荷はかかるし、時期的に定期演奏会の練習があるし、そして時間と意思はあっても保護者の理解がないと、はるばる行くことはなかなかかなわない。\n『ふだん行かないところ』で『ふだん聴けない演奏』を聴くって、さいこうに刺激的でさいこうの勉強だと思う。今回は数人だけではあるものの念願かなって現役さんを連れて行けたので、この流れでそのうちミニバス旅行的な公式イベントに発展しないかなあとか、実は考えているのだけれど。\nなにはともあれ、次のコンクール。楽しみです。また舞台裏で会いましょう。\n","date":"2014-03-31T23:44:54Z","image":"/archives/1104/img/DSC03556.jpg","permalink":"/archives/1104/","title":"北杜高校ギター部のスプリングコンサートで、日本でいちばんの合奏を聴いてきた"},{"content":"JGA コンクールでおなじみの中学校、大和市立の引地台中学校さんのギター部。\nコンクールの演奏はもう十年以上前から何度も聴いていたけど、定期演奏会は実は行ったことがなかった。スケジュールの調整がついたので初参加。3 月 29 日、土曜日のお話。\nコンクールでは出場している中学校の中でもばつぐんの演奏を披露してくれるこの引地台さん。毎年のように舞台袖で聴いていても、年々めきめきと腕を上げている感があって、ここ数年は聴くたびに引地台さんてこんなにレベル高かったっけなあと思わされる、そんな具合。\n今回の定期演奏会は、コンクールで演奏の核になっていた三年生が抜けたあとでの開催。だから当然ながら全体の演奏はコンクールのときほどのレベルではなかったけれど、部全体の “成長過程” がよくみえて、次のコンクールがひじょうに楽しみになるような、そんな演奏会だった。\n“松明の火” は去年のコンクールにむけて弾き込んだだけあって、今回のプログラムの中ではいちばん聴き応えがあった。身体が曲を覚えているのかなと、そうなるとあとは勢いで押せるし、そしてこの曲の場合はそういう弾き方がよく合う。\n“松明” の前の曲、講師の O 氏が指揮をふる今年のメイン曲らしい BWV 542、通称 “大フーガ” は、まだだいぶたどたどしかったけれど、これが次のコンクールでは “松明” のレベル以上の演奏になることはまず間違いがないわけで。いいですね、先が楽しみ。\nこの O 氏、4 小節のレッスンに 2 時間かけたとかかけないとか、細部へのこだわりがひじょうにつよい先生。これまでの弾きっぷりや弾けっぷりをみると、引地台さんは、荒く全体を短時間で作ってからあとで細部に取り組む、のではなくて、最初から時間をかけてじっくりゆっくりじわじわと錬成していく、そういう進め方のほうが伸びるタイプなのかなあと、そんな感があった。そうすると O 氏のじわじわこだわり型の教育方針（？）との相性もよさそうで、これはぜひ何百時間でもかけて至高の大フーガにしていただきたいところ。\nそして一年生の学年合奏が思いのほかすてきな仕上がり。曲がよいのか編曲がよいのかわからないけれど、『こう弾こう』という意思があると自然と合ってくる。大フーガは複雑な曲だけれど、徐々に慣れていって、こういう “わかりやすいポップス” と同じ姿勢で取り組めるようになると、部全体にとってもつよいエネルギィになりそうだなと。\n反面、重奏は全体的にちょっと危なっかしかった……。\nとはいえ、中学生の部活となると『たのしいは正義』みたいなところがあって、演奏者が楽しければそれだけで勝ち！ 合格！ っていう側面も多分にある。\n部活というのはおもしろいもので、基本的なモチベーションは『楽しいから』というただそれだけの、といいつつ実は “最強” の欲求が源泉。だからそういう組織がつくった成果に対する評価は、上手か下手かよりも、成績や品質よりも、なんだかんだいって単なる当人たち自身の “満足度” がもっとも重要なのではないかしら、みたいなことを考えている。\n現実問題、中学生というのはつい最近まで小学生だった方々であるわけで、体格やら筋力やらの身体の発達が成人と同じ楽器を扱えるところまで行きついていない、ということもありそう。\nそれでもこれだけの人数が好きであつまって楽しそうに活動しているというのは、ウマいヘタ関係なく、それだけでやっぱり “最強” なんだなあと、そんな感想。\n8 月、コンクールの舞台、MUZA 川崎のホールで会えることを楽しみにしています。\n","date":"2014-03-29T13:28:20Z","image":"/archives/1096/img/DSC03536.jpg","permalink":"/archives/1096/","title":"引地台中学校クラシックギター部の定期演奏会に行ってきた"},{"content":"要約すると、\nozie さんのお気に入りの黒いワイシャツが色落ちしてきたので 染め直し屋 さんで黒く染め直してもらったら 1 枚あたり 1,380 円とお安くすんで 仕上がりもすごくイケてた という話。\nぼくの持っているワイシャツは黒いのも白いのもほぼ ozie さんのなのだけれど、黒いのがお気に入りすぎて着まくっていたら、当然のことながらだんだん色が落ちてきた。こうなるともう着られない。\nいいかげんもう着られないけど、でもこれお気に入りのやつだし捨てたくないなあ、みたいな葛藤が出てきたので、\n色落ちした！ でも捨てたくない！ じゃあ染め直そう！ という三段論法で、染め直し作戦を進めることに。\n調べたら家庭用染料もいろいろあるようで、それで自分で染めるのも考えたけど、めんどうくさそうだし洗濯のときの色落ちとか品質とかも不安だったので、専門業社に頼むことにした。\nぐぐっていろいろ見てみて、何となく信用できそうな雰囲気があるのと他社よりだいぶ安いのとで 染め直し屋 さんに決めた。早速手続き開始。\n複数枚まとめたほうが単価が下がるので、大して色落ちしていないやつも一緒に混ぜ込んでしまうことにした。今回は黒いワイシャツを全部で 7 枚。\n2 月 21 日くらい、フォーム にどういう素材の何色の何を何枚何色に染めたいのか書いて送る。\n2 月 22 日くらい、だいたいいくらくらいになるかの返信がくる。同日、適当な箱にモノを詰めてコンビニエンスストアから発送。\n2 月 24 日くらい、届いたよ、最終的にいくらだよ、作業すすめていい？ っていう連絡がくる。同日、作業すすめてくださいってお返事。その後しばらくして作業開始しますよっていう連絡がきた。\n2 月 28 日くらい、できたので発送したよっていう連絡がきた。\n3 月 1 日くらい、到着。\n二、三週間って書いてあったのにたったの 4 日で発送された。おどろきの早さ。嬉しい誤算。\n仕上がり品質は非常に満足できるものだった。きれいにまっくろになっていて、色落ちでできていた妙なムラもきれいさっぱり。\n新品のときから気になっていた黒の色味の違い——赤っぽかったり緑っぽかったり——も、同じ染料で染めたから当たり前なんだけどすこし緩和されて仕上がってきたのでうれしい。プレスのおかげもあってつよい皺もなく、新品にもどった感がある。\n洗濯してだいじょうぶかというのが気になって一回洗濯機につっこんでみたのだけれど、ぜんぜん問題なさそうだった。\nワイシャツ 7 枚で、1.5 kg、プレス代込みでしめて 9,660 円。単価 1,380 円。\nもともとのワイシャツが 1 枚で 6,000 円くらいなので、これだけの投資でこの品質が得られるなら安いものだと思う。手間もかからず、時間もかからず。送って待つだけ。\n色落ちしたけど捨てるのが惜しい、そんな衣装に対する『染め直す』という解決策、なかなかよいものでした。みなさまもぜひ。\n","date":"2014-03-14T00:40:25Z","image":"/archives/1051/img/DSC03306.jpg","permalink":"/archives/1051/","title":"黒いワイシャツを、もっと黒く染め直そうと思った"},{"content":"ぼくがいちむじんさんを知ったのは、2007 年のこと。\n弊団体の演奏会のゲスト として来ていただいて、そこで いっしょにギターを弾いて から、もう 6 年以上が経ったようで。早いものですね。\nそのときは舞台袖で、あるいは舞台上で、おふたりの音に触れました。いいなあかっこういいなあきれいだなあと、ぽわぽわと聴いていた記憶があります。\n当時はまだピックアップシステムを使うこともなく、立ってギターを弾くこともなく、純然たる “クラシックギターの二重奏” というスタイルでした。\nそんなおふたりの、結成十周年記念コンサートに行ってきました。3 月 7 日の金曜日、会場は東京芸術劇場の小ホール。\nホールに入って、ステージ上にスピーカが立っているのとシールドが椅子に掛けられているのと足台が無いのとをみて、おお、“これ系” になったのか、と思いながら着席。前から二列目のど真ん中でした。よい席。\n『最近彼らは立って弾く』という事前情報を得ていたのである程度予想はしていたものの、繰り広げられたのは、かつてのクラシッククラシックした感じとはひと味もふた味もちがう世界。\n音の出し方とか PA の使い方とか、聴いていてぼくの中でイメージが重なったのは意外にも Rodrigo y Gabriela さんでした。あの方々から荒々しさをぐっと減らして、スピード感はそのままにクラシックギタリスト的な丁寧さと真面目さをうまい具合に混ぜ込んだような、そんな感覚。曲調が激しくても音の作りは丁寧だったし、しっとりなところのうたいかたはクラシックギタリストだけあってばつぐんにさすがな感じ。\n根はクラシックだから落ち着いて聴けるけどでも音楽は激しい、みたいな、おもしろいおとしどころのハイブリッドな音楽でかっこうよかったです。\nしかしやはりクラシックギターの PA はむずかしそうですね……。とくにピックアップを使うとなるとなおさら。\nいちむじんさんオリジナルの激しい曲では PA はよく合っていて、生音では出せない迫力でとてもよかった ((うっすらハウり気味だったのがすこし気になったけど……))のだけれど、しっとりした曲とか “クラシックギター” 用の曲とか、これは生音で聴きたかったなあと思うものもちらほらありました。RUI とかとくに……！\nクラシックギターの二重奏って、生音で緻密に組み立てる響きとか、息を飲むような緊張感とか、そういう音がだいすきなのだけれど、PA ありでがっつりアツいエネルギィをぶつけて弾くのもオイシイんだなあって思った演奏会でした。\n立って弾くのに慣れきっていないのかもと思えるシーンもちょくちょくありましたが、“いちむじん” スタイル、これからが楽しみです。生音でコテコテのクラシックな二重奏コンサートをやるというなら、それはそれで涎を垂らして行きますが！\nそして運のよいことにこんな展開になりました。\n『ご来場のみなさまの中から十名に本日のライブ音源をプレゼントします！』に当選したので個人情報渡してきた╭( ･ㅂ･)و 届くの楽しみすぎる╭( ･ㅂ･)و #いちむじん\n\u0026mdash; くろい (@kurokobo) March 7, 2014 到着お待ちしています！！！\n","date":"2014-03-10T14:24:05Z","image":"/archives/1025/img/DSC03323.jpg","permalink":"/archives/1025/","title":"いちむじんの結成十周年記念コンサートに行ってきた"},{"content":"全国職場バンドフェスティバルというイベントに誘われたので行ってきた。3 月 2 日の日曜日、会場はサントリーホールでした。一般人が上に乗れる機会はそうそうない本気で日本有数のホールですが、職場で吹奏楽をやっていれば乗れるなんてずるいと思います！\n白状すると、吹奏楽って、出身中学やら出身高校の部活のものくらいしか知らなかったのです。それ以外にもたぶん聴いてるはずなんだけど記憶に残っていなくて。で、吹奏楽部の演奏って、演奏中に立ったり座ったりする “吹奏楽っぽいアレ” とかけっこう “やらされてる感” があって、いまいちイケてないなーっていう印象、偏見が抜けなかったのだけれど。\nいやはや、なんていうかもうごめんなさいです。吹奏楽ってほんとうはこういうものだったのね。ここまでアツい音楽だとは思っていなかった。うまいところは卑怯なまでにうまい。さいこうでした。繊細な表現も迫力のあるダイナミクスもリズム感も、音づくりも。\n全国職場バンドフェスティバルというのは、全国で活動する職場バンド、つまるところ企業の中にある吹奏楽部みたいなの集まって、合同で演奏会をしましょう、というもの。今回で 3 回目？ らしく、まだ歴史は浅いもよう。それでもバターサンドの六花亭さんからトヨタさん、NEC さん、ソニーさん、天下のヤマハさんまで 11 団体も集まって、たっぷり 4 時間も演奏があったので、ひじょうにボリューミィでした。顔ぶれをみるかぎり、来年以降もながく続いていくイベントになるんだろうと思います。\nそんなわけで、ぼくは吹奏楽に関してはドがつくほどの素人だったので、プログラムを見ても有名どころ以外は知らないのばかりだったのだけれど。最初の団体からわりと『あれ、吹奏楽ってこんなイケてるものだったんだっけ』みたいな戸惑い感あふれ出る感じでした。ほんとうはかっこうよいものなんですね、吹奏楽。\n107 人でステージに乗った曲もあって、これが圧倒的なダイナミクスと派手な指揮でさいこうのパフォーマンスでした。指揮が派手でも演奏がしょぼしょぼだとひじょうに滑稽で嫌味な舞台になる ((そんな演奏はギター合奏でいくつも観てきたのよね))ものだけれど、演奏ががっつり指揮に合わせて派手にやってくれていたので、相乗効果ですごく濃密でエネルギッシュな空間になっていました。\n吹奏楽における指揮者の役割、演奏者や音楽との関係、距離感は、いわゆるオーケストラの指揮のような “崇高な” ものとは、すこしちがうようです。もっと距離が近くて、即時性があって、ほどよいパフォーマンスであること。いいものですね。聴衆側に音楽が寄ってくる心地よい感覚があります。\n吹奏楽って、コテコテのクラシックでもなく、ポップポップしているわけでもなく、ほどよくフォーマルでほどよくカジュアルなので、“音楽” の中でもおもしろい位置にいると思います。オーケストラとも軽音楽ともちがう、いいとこどりしたオイシイ位置とでもいうか。\nこの “ほどよくフォーマルでほどよくカジュアル” な居心地のよさが演奏から感じられると、吹奏楽っていうものがとたんに親しみやすい世界になるのかもしれないですね。中学や高校の吹奏楽は、たぶんこの辺の空気が全然なくて、ただ音を出しているだけだったりがちがちに緊張しているだけだったりで、それでいて立ったり座ったり右むいたり左むいたりスイングしたりの “ノッてる演技” をしようとしちゃうものだからよくなかったのかなと。この違和感のおかげで楽しめなくて苦手だったのかなと、そんなことを考えた。昔の感覚だからあんまり覚えてないけど。\n“ダンス” で攻めてきたソニーさんのワルツは、とてもワルツワルツしていてすてきでした。ワルツの三拍子をワルツらしくうたいあげるのって難しいのよね。ズンチャッチャ、とよくいわれるけれど、この『ズンチャッチャ』にこめられた一拍めと二拍めと三拍めの感覚の違いって、おそろしく深いものであるようで。頭でわかるのではなくて、ワルツだけにほんとうに身体でわからないと演奏できないと思う。同じ曲をギター合奏でいままさに練習中なこともあって、はからずもひじょうによいおべんきょうになりました。こううたえばいいのね、ぼくもがんばります。\n極めつけは大トリのヤマハさんです。パンフレットには『なかには自身で制作した楽器を演奏する団員もいる』とか書いてあるし、MC にインタビューを受けていた指揮の須川先生も『8 割くらいが楽器の設計か研究か制作をしているひと』みたいなことをお話されていたので、はじまる前からもはや卑怯というかチート感はんぱなかったのだけれど、演奏もやっぱり卑怯なまでにはんぱなかった。\n一曲目はもともと分かりにくい曲だったこともあってふわっと終わってしまった印象があって、ヤマハさんでもこんなもんなのかなーって感想をもったのだけれど。\n二曲目の Sing Sing Sing の、冒頭のパーカッションが入った瞬間、『あ、これ本気ですごいのくる』と直感で確信して、そこからテンションあがりっぱなし。そして事実、本気ですごかった。\nSing Sing Sing という曲自体、じつはあまり好きではなかったのです。というのも、ぼくが高校生のときにギターで弾いたのがひじょうにつまらなかった（ごめんなさい）から。吹奏楽の定番なのは知っていたけれど、だからプログラムをみたときもさいしょはこんな定番曲じゃなくてもっとなんかイイやつやってほしいなーみたいなことを思ったわけですよ。\nが、けっきょく、ぼくの中にある Sing Sing Sing 像がスーパーしょぼしょぼだっただけのようでした。だからヤマハさんの聴いて、マジかよこんなイケてる曲だったのかよこれ、ぼくらの高校のときのアレなんだったんだよ、時間かえしてよクソが、みたいなそんなかんじ。もう実はわりとこっそりひそかに涙目になりながら聴いてた。かっこよかった。プリマさんごめんなさい。\nうまく言えないけど、“音楽” による “表現” とはこうあるべきだ、みたいなお手本を見せつけられた感覚でした。まじめな部分も、あそぶ部分も、隅から隅まで余すことなく、演奏者ひとりひとりの全身から “表現欲” みたいなものがむわんむわんと押し寄せてきた感じ。ほんとうに、ああいう演奏がぼくもしたいと、切に。\nドラムさん、圧巻でしたね。何をしてももうあのひとなら許されるよな、みたいな圧倒的なパフォーマでした。高ぶりすぎてジャンプしちゃうとかかっこいいです。惚れた。\n音楽をやっているひとの『他団体の演奏会に出かける』という行為には、いくつか種類があります。自分がやっている音楽と同じ分野の演奏会には比較的気軽に足を運ぶ気になるけれど、あんまり関係のない分野の演奏会って、なかなかいく気になれないとか、よくありますよね。\nでもやっぱりこう、自分のと同じ分野かなんていう狭い枠で終わらないで、“音楽” っていう枠でとらえてあっちこっち行きまくりたいし行きまくるべきだと思いました。聴いただけ世界は広がるし、広がっただけこれまで自分が観ていた世界の狭さにも気がつくものです。自分の演奏が現状のままでよいならどうでもよいのだけれど、そうでなくてもっといろいろな音が出せるようになりたいのであれば、いろいろな音は聴かなきゃです。自分が井の中のなんとやらであることを自覚するべきだし、それ以前にまずは自分がただの蛙である可能性を少しでも考えないとです。\nここ何年かいろいろと足を運ぶようになってるけど、まだまだいろいろあるなーと、そんなことを思った日曜日でした。よい日でした。来年も行きたいです。\n","date":"2014-03-02T15:18:29Z","image":"/archives/991/img/DSC03322.jpg","permalink":"/archives/991/","title":"全国職場バンドフェスティバルで、さいこうにイケてる吹奏楽を聴いてきた"},{"content":"ちょっと前の話になるけれど、2 月 11 日に、専門学校国際新堀芸術学院に所属する在校生の有志で行われる定期公演 ((“有志” という概念に “定期” はあまり似合わない気がしたけど))に行ってきました。\n本家たる新堀ギターフィルハーモニーオーケストラ（N フィル）とか新堀ギターアンサンブル（NE）の公演には何度となく足を運んでいるけれど、新堀学院の学生さんたちの演奏を聴く（観る）のは初めての機会。“学生版新堀” という意味では NKG のコンクールでの演奏が近いのだろうけれど、あれは新堀グループの先生方が指導してるってだけで、新堀学院の学生というわけではないですし。\nさて、新堀さんところの学生ということは、将来的に N フィルなり NE なりを（少なからず）目指している方々である（たぶん……）わけで、そういう意味ではこれは N フィルなり NE なりの “あの” 演奏が作られる “途中経過” が観られる演奏会でもあるわけです。学生が “あれ” を目指したときにどうなるのかというのは NKG のコンクールで観てはいたものの、はてさて本家でがっつり学んでいるとどうなるのかなあと楽しみなのでありました。\nが。\n結局、PA ががっかり品質すぎて、演奏云々以前のところで耳が止まってしまいました。\nこのエントリは、そんなお話です。ギター合奏と PA について。\nエレキギターに代表される “電気的な増幅ありき” の楽器とちがって、クラシックギターはそれ単体で電気的な増幅なく音楽を作れる楽器です ((もちろんどちらが優れているという話ではなく、そういう楽器であるというだけの話です、念のため))。だからクラシックギターを “クラシックギター” として使う以上、本来は PA が無くても音楽として “完成” させられるはずだし、“完成” されているはずでした。\nPA が無くても完成されているのだから、それでも PA が登場する理由は、“生音だと小さくて聴こえないから” というごくごく常識的なところに行きつくはずで、そうすると PA に求められる仕事は、既に “完成” されている音楽を、極力 “そのまま拡大する” ことだけです。ここでは過度な脚色は御法度で、『演奏者が作った響き』『演奏者が意図した響き』をそのまま観客席に届けることだけが考えられるべきだ、というのがぼくの考え。\n今回の公演で入っていた PA では、全奏者のギターすべてにコンタクトマイクをつけていました ((新堀さんところは今回に限らずだいたいこのスタイルですね。人数が多いときは前列とか主要メンバにだけマイクをつけてあとは生音です))。スピーカから出てくる音は当然、全ギターにつけられたコンタクトマイクの拾った音を、PA 担当さんがコンソールでミキシングしたもの、です。ホールのキャパシティを考えると、生音が届くのは客席の前のほうの一部くらいで、その一部ですらスピーカからの音のほうがおそらく大きく聴こえるから、生音の出番は今回はもはや無いようなものです。\nさて。\n演奏者が舞台上で、至高の音楽を至高のバランスで作り上げていたとします。演奏者は自分の耳と感覚を信頼して、隣のひとといっしょに、指揮者といっしょに、向かいのひとといっしょに、舞台の空気の中に自分の音をそっと置きます。そこにはギターならではの芳醇な響きがあって、そして独奏では味わえない合奏重奏ならではの重厚さや繊細さがあって、それはさらにホールの自然な残響を伴って会場中にひろがります。その空気の中に自分が居る感覚、空気を自分が作っている感覚こそが至高で、また演奏者のこのうえない喜びと快感と、楽しさの源泉です。\nところが、PA のマイクは、そういうオイシイ響きはあまり拾ってくれません。コンタクトマイクですから、ギターに片耳をぺたっとくっつけて聴いている状態に近い。ギターに密着しているから、拾うのはギターから外に出た音ではなくて、生々しい “振動” そのものです。だから本来拾わなくてよい音、離れていれば聴こえないのに密着しているからこそ聞こえてしまう音、本来楽器として響くようにはできていない音——たとえば布のこすれる音とか、ギターにさわってぺたぺたする音とか、弦をこする音——まで無関係に拾ってしまいます。そしてそれはギターの音だろうがただのノイズだろうが、機材にとっては無関係で、ひとしく拡大されてスピーカから流れてきます。さらにマイクはモノラルで、スピーカは二つあるから、無理矢理説明すると、スピーカのコーンの位置に、ギターにつけられたマイクの位置の空間が全ギター分重なり合って存在している状態、みたいな感じです。そしてそれが左右にひとつずつある。さらにマイクからスピーカにいたるまでに通る機材ごとの特性で、特定帯域ばかりが強調されたりある音が全然拾われなかったりなどの影響も加わります。\nだから、舞台上で作られているひびきと、スピーカから流れている音は、コンタクトマイクだけをつかってどうにかしようとする限り、もうまったくの別物です。舞台上で生で作られる音は、舞台上で生で聴いてこそ最高の音で、そしてそれはマイクを通してスピーカから流したときに最高の状態になる音とは、まったく違うものです。人間の耳にきこえるそのままを電気的に取り込むのはひじょうに難しいのです。逆に言えば、人間の耳とそれを解釈する脳は信じられないくらい優秀です。\nしかも合奏です。演奏者同士で舞台上でどれだれ連携してどれだけ繊細にバランスのとれた音を出していようが、PA 担当さんのミキサにはギターごとに別々の音として入ってきます。それをどう料理するかは PA 担当さん次第で、客席で聴こえる音は、だから “舞台上でつくられた音” ではなく、あくまで “PA を通した音” でしかありません。PA は、演奏者の緊張感やバランス感覚とはまったく別の次元で存在します。観客に聴こえる音のすべてを操る、いわば神です。\nマイクが拾ったそのままを混ぜて流されたら、ひたすらにぐちゃぐちゃです。何を弾いているのかわかったものではなくなります。\nだからこそ PA には、精緻なバランスで左右へ振り分けたり、重なって混ざってしまう音がきれいに分離するようにイコライジングしたり、要らない音を削ったりして、舞台の上で作られているはずの音を再現する、そのように “細工” するスキルとセンスと責任が求められます。すでに完成されている音をそのまま届けるというのは、単にマイクから入ってきた音をそのまま垂れ流せばよいというものではないのです。完成されている音を、“そのままであるかのように聴こえるように” 調整してスピーカから流す必要があります。\nあの組織が、ぼくみたいに『PA がなくても音楽として完成している』という考えではなく、『PA があって初めて音楽として完成する』という考えでいる可能性もあります。クラシックギターを、旧来の “クラシックギター” としての枠組みにとらわれずに、『電気的な増幅ありきの楽器』として新しい使い方を模索しているというのであれば、それはそれでおもしろそうだし、先が楽しみな変化です。\nが、そうならそうで、なおさらそれなりの音作りをしてほしかった。あれではただ『生だと音小さいからマイクつけるね』『全部に同じマイクついてるし全部同じ音で出しておけばいいよね』みたいな、雑な思想にしか見えませんし聴こえません。哲学を感じない、みたいなアレです、イケてないです。\n意図的にリバーヴをかけていたようにも聴こえました。音楽ホールにおいては禁じ手とも言える気がします。響きって、いやいやそういうのではなくてね、という感覚。もしかしたらフィードバックの結果かもしれないけれど、それならそれで調整はするべきで……。\nそしてギタロンの PA がとくにひどかった。ギターアンサンブルを録音したことがあるひとならだれでも一度は悩まされるあの超低音域のぼわつき、耳に迫り来る音圧のことです。今回はあのぼわぼわぼわぼわしたのが大音量でホール中に響き渡ったおかげで、それ以外の音が完全にぶちこわしでした。マイクにマイクの性能以上のことをさせてはいかんのです。\nもしあのコンサートが、中の方々にとって『大成功』で、そして内部で『とてもきれいな音だった』と評価されているとしたら、その中にいる学生さんたちは、本来の “クラシックギター” としてのクラシックギターの音づくり、響きづくりを、まともに学べないことになってしまう気がします。純粋に “クラシックギター” を学びたい方々にとっては向かない環境なのかなと。新しい楽器としてのクラシックギターを学ぶなら、逆にあそこしかないんでしょうけれど。なんていうか、『“クラシックギター” の専門学校』ではなくて、『クラシックギターを含む様々な楽器群を道具として使った総合芸術の専門学校』っていう印象でした。\n“クラシックギター” の音づくりはそんな感じだったので、客席にいたまま『マイクで拾われる前の響き』を脳内で取り出すのも難しくて、演奏面でのコメントはとてもしづらいという結果になりました。\nただ、断片的に見えた限りでは、独奏は丁寧だったし、合奏も楽しそうで、たぶん舞台の上の彼らにとっては『よい音楽』ができていたのだと思います。本来は、そういう『よい音楽』を、練習とは比べ物にならないほどの響きで楽しめるはずの場がこういう公演でありコンサートホールであるはずなのだけれど、それが PA に乱されてしまってひじょうにもったいない ((舞台上にモニタスピーカもあったので、演奏者も演奏しにくくなってたのではないかしらという心配もありました))なあと、PA なしで聴きたいなあと、そう思わされるのでした。生音でじゅうぶんひびくホール ((藤沢校の楽友ホールもギターに向いてない音だし、環境がなかなか難しいのかな))で、しっかりがっつり生音で演奏会をしてみてほしいところです。\n皮肉なことに、エレキギターやドラム、キーボードなどの演奏、いわゆる “バンドサウンド” のときの PA はとてもイケていました。正直、“クラシックギター” は使わないほうがすっきりするんじゃないの、とさえ。\n学生の手作りな演奏会だし、そこにそう多くのことを求めてはいけないのも承知しているけれど、PA 卓に立っていたのがおじさまだったので学生云々はあまり関係がないのかなと。もしかしたら PA 卓のおじさまは学校側から渡される 2 ミックスを単に流すだけでいいですって指示されたホール側の方だった可能性もありますが。\nギター全部にマイクをつけるのではなくて、例えば三点吊りの音をそのまま流すとか、舞台手前にアンビエンスマイクを立ててその音を流すとか、そのほうがずっとよかったのではないかと思います。“マイクの音” ではなく、ぼくは “演奏者の音” が聴きたかったです。演奏者個人個人のポテンシャルは大きそうだっただけに、PA の残念さが全部を残念にしてしまっていて、それがとても残念でした。\nギターは音量の小さい楽器ですし、大きなホールで演奏をするなら PA は欠かせません。それでもよい PA は、PA の存在を感じさせないほどにほんとうによいものです。ギターひとつひとつの音がそのまま大きくなったような、そういう PA も世の中にはたくさん存在します。\n演奏を活かすも殺すも PA 次第と、えらいひとは言いました。PA が悪ではなく、演奏の魅力を最大限に引き出すものであってほしいと、そういうことを考えた一日でした。\n","date":"2014-02-26T14:17:41Z","image":"/archives/953/img/DSC03288.jpg","permalink":"/archives/953/","title":"新堀芸術学院の春期定期公演で、ギター合奏と PA について考えた"},{"content":"いろいろいった気になってるけどどれだけ行ったんだっけね、というまとめ。まとめたら思っていたより行っていたのでうれしい。\n来年も同じくらいかそれ以上には行きたいけどどうなるかしら。\n1 月 20 日（日）- 原善伸デビュー 40 周年記念ギターリサイタル＠ヤマハホール 先生にお誘いを受けて聴きに。歴史を感じる演奏。ベテランがベテランたる理由はやはりあるのだと思うことしきり。\n2 月 24 日（日）- M\u0026rsquo;s class ピアノ発表会＠大倉山記念館 音楽の才能があふれすぎている高校生の友人がピアノの発表会に出るというので聴きに行ってきた。ぎたさんのぱとり勢もゲストで登場。ピアノの弾きっぷりがかっこよかった。\n2 月 27 日（水）- 横浜国立大学音楽専門領域卒業演奏会＠横浜みなとみらい小ホール 作曲ゼミの友人が卒業制作で書いたギター五重奏曲の演奏者として参加。あと録音やさん。縁ってのはおもしろいものですね。よい曲だったしもうひとりの作曲ゼミの子の作品もすごくイケてた。平日だったから会社休んだ。\n3 月 31 日（日）- 北杜高校ギター部スプリングコンサート＠北杜市オオムラサキセンター ずっと行きたかった北杜さんの演奏会。ようやく行けてほんとうに楽しかった。いろいろとごあいさつもできて、車を出して遠出した以上の価値はあった。\n4 月 4 日（木）- 多摩高校ギターアンサンブル部定期演奏会＠横浜みなとみらい大ホール かつて自分がいた部活の定期演奏会。会社を休んで朝から運転手兼写真撮影担当兼録音担当。毎年の楽しみのひとつ。来年からはお客さん？\n5 月 13 日（日）- ギターフェスティバル＠代々木上原けやきホール 長谷部先生の門下生の方々の演奏会。ゲストのクアトロ・パロスさんを聴きに。さすがのクオリティ、安心して楽しく聴ける四重奏。\n6 月 8 日（土）- 東京農業大学＆専修大学ジョイントコンサート＠多摩市民館 フォロワさんが出るとのことだったので聴きに行ってきた。ザ☆大学のノリ！ みたいな演奏会。たのしそうだった。ちょっと形式をだいじにしすぎな感もあって、もっと自由にしていいのになあとは思った。\n6 月 9 日（日）- NHK 交響楽団定期演奏会＠NHK ホール いわずもがな。聴きに行きました。惑星さいこうです！\n6 月 12 日（水）- サントリーホール室内楽アカデミーゲストコンサート＠サントリーホールブルーローズ プロアマ混在でいろいろなひとがいろいろ弾く室内楽の演奏会を聴きに。アマチュアといっても音大生なんだけど、それにしてもプロとの差が……！ はからずもベテランと若手の聴き比べになってしまった感。\n7 月 13 日（土）- 洗足学園音楽大学 NATSUON！ 2013 クラシックギターコース演奏会＠洗足学園音楽大学 録音担当兼お客さんとして。音大生によるギターアンサンブルというめずらしい演奏会。各人のポテンシャルはすごかったけど、楽器にも新しい先生にも慣れていない感が出てて、ちょっと新鮮。\n7 月 13 日（土）- ソニー吹奏楽団定期演奏会＠大田区民ホールアプリコ大ホール 聴きに。上の洗足さんと同じ日だったので、朝は洗足で録音の準備して、こっちに移動して数十分だけ聴いてまた洗足に戻る、というスケジュールだった。エヴァンゲリオンを聴けたので満足。吹奏楽もかっこいいですね。\n8 月 25 日（日）- 全国学校ギター合奏コンクール＠横浜みなとみらい大ホール 毎年の楽しみのひとつ。例年どおりに録音やさん兼ステージ関連いろいろスタッフとして参加。出場校の中高生にも知人友人が増えてきたので、どの学校も他人の気がしない。袖でヘッドホン越しに音を聴くのが毎年恒例。来年のぼくはどういう形で関わるのかしら？\n9 月 7 日（土）- 多摩高校ギターアンサンブル部文化祭コンサート＠多摩高校 演奏を聴きに。引退した 3 年生やそれより上の OB 勢ともわちゃわちゃできるよい機会。たのしい。\n9 月 22 日（日）- 相模原中等教育学校クラシックギター部オータムコンサート 演奏を聴きに。実は構内に入るの初めてだった。ここでまたお知り合いが増えました。『もしかしてくろいさんですか！』『はい！』がたくさん。うれしい。\n10 月 6 日（日）- 全日本ギターコンクール合奏部門学校の部＠江戸川区総合文化センター NKG 主催。昔は行くとアウェイ感すごかったし社会科見学感あったけど、最近は平気な顔をしてこっそり客席におさまっている。目指す方向がいろいろちがっていておもしろい。行くといろいろ考えてしまいますね。そして語り合う夜の Togetter ができあがる。\n10 月 6 日（日）- アコースティックギターサミット 2013＠めぐろパーシモンホール 今年からはじまったイベントらしい。誘われたのでお客さんとして行ってきた。昼間からいろいろワークショップなど開かれるもよう。夜のコンサートメインでいった。普段絶対いっしょに並ばないであろうメンバが順に演奏する感じ、贅沢感はんぱない。来年も行きたい。\n10 月 13 日（日）- アンサンブル・ジターノオータムコンサート＠武蔵ホール フォロワさんにお久しぶりに会いに行きがてら聴きに。かわいいきれいなホールだった。少人数でめいっぱい楽しむ感覚、観ていてたのしい。ぼくらのコンサートにも何名か来ていただけたのでうれしいです。\n10 月 14 日（月）- 内本信裕ギターリサイタル＠和光大学ポプリホール鶴川 高校の先輩のデビューコンサート。ドイツでギターを学んでいるだけあってさすがの安定感。きっちりキメてきてきもちがよい。二重奏も三重奏もたのしかった。\n10 月 19 日（土）- イ・ムジチ合奏団＠サントリーホール大ホール もう何回目かわからないけど何回聴いてもさいこうだ！！ 生音とはかくあるべき、というのを強烈に魅せつけてくれる。コンサートマスタが完全にアンセルミさんになって、また雰囲気が変わってきてイケイケ感あって楽しいし、アンコールの赤とんぼはもうなんていうかほんとうに全身にしみわたってすばらしい。ああいう音が出したいなあと心の底から思える音。お金かかるけどまた日本に来たらまた行く。\n10 月 27 日（日）- 日本ギター合奏フェスティバル＠練馬文化センター小ホール クアトロ・パロスさんと UnisOno さんを聴きに。といいつつほかにもいろいろオイシイ合奏団があって見た目にもおもしろい。加藤先生の団体に惹かれた。この日の夜は多摩・相模大野・所沢・芝各校の OB やら元講師やらが入りまじった飲み会になっておもしろかった。\n10 月 28 日（月）- クアトロ・パロスジョイントコンサート＠GG サロン クアトロ・パロスさん、東京 GE さん、前日気になったザ・ステアさんを聴きに会社帰りに寄った。オンゲンカーさんは初見だったけど、いちばん右の彼がイケてた。2 曲目かっこよかった。ゲストのオルガさんは私服感がつよくて、中島先生のほうが目立ってたよね……？ パロスさんが演奏した佐藤弘和さん作曲の雲の詩って曲がさいこうでした。ああいう情景描写系は大好物です（三千院とか）。\n11 月 3 日（日）- JAEM 秋期ギター発表会＠和光大学ポプリホール鶴川 普段いっしょに合奏をしている方々や関係各位の、普段とはちがった一面が観られる場所。お客さんとしてお邪魔しました。レベル感いろいろだったけど音はよい。ぼくも出ようかなとか。\n12 月 1 日（日）- 芝学園ギター部ウィンターコンサート＠芝学園 これも前からいちど行ってみたかった演奏会。東京タワーのふもとまで聴きに。昔聴いたときの強烈なインパクトはやや薄れた印象。ちょっとおとなしくなった？ ノリはいいし観ていて楽しいけど、魅せどころってののつくりかたがむずかしいそうだなと。そして音響面がやっぱり場所柄きびしい……。自分の学校の中に演奏会が “できてしまう” 施設があると外に出にくくなるってのはあるのかもだけど、響きのいいホールでやるとたぶんもっと音よくなるんじゃないかしら、と。でもなんていうか、心の底から楽しんでます感が出ててすごくよい。たのしい。\n12 月 8 日（日）- 洗足学園音楽大学 FUYUON！ 2013 クラシックギターコース演奏会＠洗足学園音楽大学 夏につづけて冬の演奏会を聴きに。夏のころの “不慣れ感” はだいぶ薄れて合奏ならではの音のつくりかたにもなれてきたのか、安定感があった。もう少し物理的に高音系の楽器が増えるとバランス取れそう。この日の竹内先生の Surge III 全楽章は日本初演。Surge V で落ち着きを見せた “竹内節” がややおとなしめながらも復活していて、よい曲だった。\n12 月 15 日（日）- 新日本ギターアンサンブル ギタークリスマスコンサート 2013＠和光大学ポプリホール鶴川 出演。ひたすらに楽しかった。詳細は 前のエントリ で。\n12 月 21 日（土）- 新日本ギターアンサンブル ギタークリスマスコンサート 2013 シルバーマウンテンオープニングコンサート＠洗足学園音楽大学 出演。攻略しがいのありそうな会場。大学側のスタッフさんが大学として公式に録画やら録音やらしてくれていて、そのうち YouTube に全編 1080p で載るらしい。うれしいやらこわいやら。\n以上！\n抜けがあるかもしれないけど、今のところぜんぶで 25 でした。平均でひと月に 2 回、なかなか充実してた感があってよいかんじ。行ったら行っただけ世界も視野も広がるし、純粋に楽しいし、そしていろいろお勉強にもなる。弾き方、音のつくり方。ギターにかぎらず、ピアノだって弦楽だって吹奏楽なんだってギターに活かせる要素ってあるはずだし。\n来年もぶいぶいいろいろなところに行きたいので、こんなのあるよこんなのやるよ、あんなのあるらしいよ、その他もろもろお気軽にお声かけください。万難排してどこでも行きます。\n","date":"2013-12-24T14:49:33Z","image":"/archives/929/img/DSC08368.jpg","permalink":"/archives/929/","title":"ぼくが 2013 年に聴いたり観たり出たり手伝ったりした演奏会まとめ"},{"content":"今月の 15 日（土）と 21 日（日）、ぼくが所属するギター合奏団、新日本ギターアンサンブルの演奏会があった。\n今年もいつもどおりにコンサートに出て、いつもどおりに終わって、いつもどおりに年が明けるんだろうなあと、そういういつもどおりの展開を予想していたのだけれど。\n高校一年生のときに初めてギターに触ってから 11 年目、今の団体に所属して 7 年目。\nようやく、ギター人生で初めて “納得のいく” 演奏会になったなあと、そういう感覚を得た。\n別に演奏が完璧だったわけでもないし、指がきれいに動いてくれたわけでもないし、ミスをしなかったわけでもないのだけれど、納得ってそういう意味のではなくて、ぼくはこう弾けばいいのかと、こういう音をつくればよいのかと、これがぼくのスタイルなのかと、そういう納得感。これがぼくの弾き方です、これがぼくのスタイルです、そんな説明ができそうになるような、そういう納得感。\n開演の時間になって、袖のドアがあいて、そのまま自分の席についてひといきしたら、なんというかこう、悟りでも開けたかのように、ああこれはキたなと、瞬間的に環境と融和した感覚が降りてきた。空気がふわっと変わって、あとはもう音の流れと空気と音楽のなかを圧倒的な没入感を味わいつつ最後の一音までさいこうにきもちよく泳いでいた、ような感覚。自分たちが出したその音を、いまその瞬間に客席から聴きたいと思った。もちろん無理だけど。\nひたすらにたのしかったです。これまでの 11 年の中で、まちがいなくさいこうだった。\n社会人は遊ぶ時間がないとか社会人は忙しいとか、そういう風に思われるのがすごく嫌で、社会人でも遊ぶ時間はあるよと、自分の時間はじゅうぶんに作れるよと、そういう生き方もできるんだよと、現実は世間で思われているほどつらくないんだって自分の身体で証明したかった感もあって。だから練習をしながらもいろいろな演奏会に遊びに行ったしいろいろなひとに会いに行ったし、やりたいようにやって遊びたいように遊んで生きたいように生きていたのだけれど。\nそうして生きる中でいろいろな演奏者の音をきいていろいろな弾き方をみて、これかっこういいなとか、これかわいいなとか、これかっこうわるいなとか、これきもちがいいなとか、これださいなとか、これうるさいなとか、これあざといなとか、これきれいだなとか、そういう少しずつの感想、ほんのちいさな納得をあつめて食べて飲みこんで吸収して、自分はこうしてみようああしてみようと、これまで食べたものをまぜこぜにしてこねくり回してみたのが、たぶん自分にとってはよかったんだとおもう。やっとストンと落ちた。\nとはいうものの。\nギターアンサンブルってすごくマイナだから、視野を広げてみたところでそこまで世界は広くない。大きな派閥めいたものもあるけれど、それだって別に大きくない。ただでさえちいさい世界なのに、この世界のひとたちって自分の居る団体のことしか見ていない感がすごい。自分以外の団体のことはどうでもよくて、自分たちの演奏会には自分たちの身内ばかりをお客さんとして呼んで、内輪で褒め合ってきゃーかっこいーきゃーすてきーきゃーじょうずーって言い合ってる感じ。\n承認欲求満たしあうだけならそれでいいんだけど、それならもっとつよく承認欲求が満たされたらもっときもちがよいし、じゃあもっと強く承認欲求を満たすには、全然関係ない無関係すぎて予想外すぎるほどに無関係なコミュニティのひととか世間的にあんまり仲がよくないと思われている別の派閥っぽいひととかに承認してもらえばいいんだ！ と思わなくもないわけで、じゃあ全然関係ない予想もつかないほどに無関係すぎるコミュニティのひととか世間的にあんまり仲がよくないと思われている ((実際そんなことないんだけどね))別の派閥っぽいひととかを演奏会に呼ぶにはどうしたらいいのかなあということをぽやぽや考えている。ひとをつかって宣伝をするかぎり『知っているひとのことしか知らないよ』ってなるのは当然なわけで、来てほしい層にリーチするには来てほしい層にリーチしそうな広報媒体を使わないといけないよね。いたずらに広報してひとが来すぎても、受け入れられるだけのキャパシティがない（現状毎年 500 人だし空席ほとんどなくなるし）から、それはそれで別の問題が出てくるけど。\nとはいってもやっぱり身近なコミュニティ（いろいろな中学高校大学のギター部のみなさまとか、その OB のみなさまとか）はとてもだいじだしだいすきです。そういうコミュニティの方々に、ああいう音が出したいああいう演奏がしたいああいう弾き方がしたいああいう団体に入りたいと、そういう “身近な目標” 的存在として認識してもらえるような、そんな団体としての位置づけはめざしていきたいところです。『ビッグになってやるゼ☆』とか『デカいことやってやるゼ☆』とかいうのが具現化した未来がきたとしても、近いコミュニティはいちばんだいじ。\nそんなわけで、ようやく “産まれた” この感覚が、もうすこし確かで、もっと質の良いものになるように、来年もたのしく生きようと思います。\n","date":"2013-12-23T12:24:49Z","image":"/archives/909/img/DSC02451.jpg","permalink":"/archives/909/","title":"G.C.C. 2013"},{"content":"過去のついーとから話題ごとにいくつか抜き出して並べる。\nこういうついーとを書いているときって、いつかこのネタを膨らませてブログに書こうとだいたいは思っているのだけれど、実際そうはいかないものですね。\n弾く、あるいは演奏するという行為 脳を使おう、脳内の音楽を極上にしよう、想像の質を高めよう、という話。\n脳は万能なのですヽ(・∀・)ゝ 妄想の世界ならどんな弾き方だってできるしどんな音だって鳴らせるのですヽ(・∀・)ゝ 考えるだけで本当に鳥肌が立つような、そういう理想的で究極に至高で最高な音を、脳内で演奏できるようになろうヽ(・∀・)ゝ 出し方はあとまわしでいいのですヽ(・∀・)ゝ\n\u0026mdash; くろい (@kurokobo) August 5, 2013 人間のボトルネックは身体動作を伴うアウトプット部分で、決して脳ではないのですヽ(・∀・)ゝ 脳の万能な力を信じて、枷をはめずに、脳に自由にさせてみるのですヽ(・∀・)ゝ 言語化しようとせずにヽ(・∀・)ゝ 言語化すると言語化できる範囲の外に出られなくなりますヽ(・∀・)ゝ\n\u0026mdash; くろい (@kurokobo) August 5, 2013 脳内で至高の音を出す自分の姿が想像できたら、それを現実世界でトレスしますヽ(・∀・)ゝ 音を出した瞬間、きっと理想とのギャップに幻滅するヽ(・∀・)ゝ そこからが練習だヽ(・∀・)ゝ りそうにひたりげんじつをみつめもだえくるしんであがくのですヽ(・∀・)ゝ\n\u0026mdash; くろい (@kurokobo) August 5, 2013 宗教じみてきたけどわりとそういう感覚、ぼくは大事にしてる。想像できないものは現実にできない\n\u0026mdash; くろい (@kurokobo) August 5, 2013 『頭を使って演奏する』ことは、脳にたくさん想像と妄想をさせながら弾くことに似ている。想像する作業と指を動かす作業は半分くらい連動してて半分くらいは別人格。でもどっちもぼくの世界\n\u0026mdash; くろい (@kurokobo) August 5, 2013 手癖、フォーム 三年前にはこんなことを書いていた。どうでもいいけど文中のカッコが半角なのが気になる。当時は当時のポリシィがあったんだよ……！\n毎日のように一緒に居る『先輩』の一挙手一投足が『後輩』に与える影響はものすごく大きい。意図せずとも結果的に『伝承』される部分が増えて、理想的とは言えない『癖』が下の代まで続くのはよくあること\n\u0026mdash; くろい (@kurokobo) June 16, 2010 部活という短期レンジで考えればその『癖』も生き残る上でそこまで問題はない(とはいえ赤信号をみんなでわたる系でもある)けど、部活を出て基礎のできた社会人に紛れ込むと、その『癖』はとたんに足枷になる。そして定着しすぎているからなかなか外れない\n\u0026mdash; くろい (@kurokobo) June 16, 2010 なにがいいたいかっていうと、引退して五年経ったいまでも、高校時代のフォームとかタッチの正しくない癖の改善に苦労してますよってこと\n\u0026mdash; くろい (@kurokobo) June 16, 2010 音質を求める 脳の話に近いけど、やっぱり先に進むには理想がないとね、という話。\nアルトギターの音はまさに百聞は一見に如かずってやつでな∩(・ω・)∩ 口でああしろこうしろいうよりも、音を出せるひとが一緒に弾いて音を聴かせるのがいちばん近道と思ってる∩(・ω・)∩ 自分が目指すべき音を知らないと試行錯誤のしようもないからね∩(・ω・)∩\n\u0026mdash; くろい (@kurokobo) February 11, 2013 手の使い方、爪の入れ方、なんでも口で言うのは簡単なんだけど、言われた方からすれば『それをやって、じゃあ結果的にどういう音になればいいのよ！』っていうのがわからんと練習しようがないわけでな∩(・ω・)∩\n\u0026mdash; くろい (@kurokobo) February 11, 2013 ぶっとんでうまいひとはうますぎて参考にならないから、身近なところで目標を見つけるとよいです∩(・ω・)∩ あのひとみたいな音が出したいなあと、ぼんやりとでもいいから自分のなかで素直に思えるようになったら第一歩である∩(・ω・)∩\n\u0026mdash; くろい (@kurokobo) February 11, 2013 誰を目標にしたっていいし、そして誰が目標だって表明する必要もない∩(・ω・)∩ 後輩や同期や先輩のなかでよい音をだすなあと思えるひとがいるとよいですね∩(・ω・)∩ だいじなのは、ほどよい自信過剰とほどよい謙遜とをまぜこぜにして、きれいな音だなあ素直に思えることです∩(・ω・)∩\n\u0026mdash; くろい (@kurokobo) February 11, 2013 ……というわけで学生諸氏にとっての身近な目標たりうる存在になれたらうれしいなあとは思ってるんだけど、そもそもぼくが自分の音を聴かせる機会があまりないわけでどうしようもないな∩(・ω・)∩ 『俺の音を聴けー！！』ってこっちからずかずか行くのもただの傲慢なくそ野郎だし∩(・ω・)∩\n\u0026mdash; くろい (@kurokobo) February 11, 2013 合奏団の中に信じられないくらいよい音をだすアルト弾きさんがいてな……∩(・ω・)∩ 入団後しばらくしてほあああああって衝撃うけてからずっと目指してるんだけどなかなかなかなかなかなか∩(・ω・)∩\n\u0026mdash; くろい (@kurokobo) February 11, 2013 弾いてるときはこうなる @wgmg329 正しい表現かどうかわからないけど、大げさにいうとその時々にリアルタイムに生成される音楽に没入しすぎてアドレナリンだばだばでトランス状態になるような感覚、見た目でどうだっていうより自分が酔える酔えないの世界よね、弾き慣れて余裕出ると音で会話できるでしょ？ あの感じ\n\u0026mdash; くろい (@kurokobo) October 28, 2012 音楽にも機嫌ってものがあってですね。結局ぐるぐる回ってそれは自分の精神状態のあらわれなんだけどさ。それがこう、すっと伸びてくると気持ちが良いんです。でも今日は、なんだか空回りしたんだ(´ω`)\n\u0026mdash; くろい (@kurokobo) May 21, 2011 殊に音楽に関しては、理論を知らず感覚だけで生きてきたから、とりわけ稚拙であるわけだ。でも感覚は大事にしたいとも思い、だからぼくはぼくのできることをやろうと\n\u0026mdash; くろい (@kurokobo) May 21, 2011 楽譜を読もう 『楽譜を見ないでもおたまじゃくしの通りに音を並べられるようになった』っていうだけでは、まだまだ暗譜って言えないと思うんです。\n暗譜して弾くのも大事だけど、そうかといって練習中に楽譜見ないのがエラいかっていうと全然そんなことないの。もっと楽譜を読もう、だいじなことがたくさん書いてある(\u0026#39;ω\u0026#39; )\n\u0026mdash; くろい (@kurokobo) May 11, 2013 音楽をひとつの言語だとして、楽譜を文章と捉える。アルファベットがひとつひとつの音符。フレーズは単語、流れは文章。音読が演奏、朗読が表現。日本語の文章をぼくらは当たり前のように読めて表現できるけど、音楽っていう言語はそうもいかない。シュタイドルさんは、とても流暢な音楽を話す。\n\u0026mdash; くろい (@kurokobo) February 26, 2011 日本語の文章は、見た瞬間語句の句切れもわかるし、読むときに付けるべき抑揚もわかって、緩急もわかって、喉を操って好きに表現できる。音楽でもそのくらいできればいいんだな。楽譜読んでて『これは\u0026quot;お\u0026quot;……次が\u0026quot;ん\u0026quot;…………が……く……、あ、おんがく、か』とかそんなんじゃだめなんだな\n\u0026mdash; くろい (@kurokobo) February 26, 2011 合宿の活かし方 一年前にも facebook で同じことを書いていた。というか、一年前のをコピィしてついーとした。\n\u0026quot;合宿でできるようになったこと\u0026quot; は、別に多くなくてもよいのです。\u0026quot;将来的にこうなればよいと理解できたこと\u0026quot; が、たぶん合宿の利益の大部分。で、合宿がおわったあとの練習が、理想を現実にしていく作業\n\u0026mdash; くろい (@kurokobo) July 24, 2013 去年は合宿に二日間だけ参加してたのでした。\nぼくの行動に起因する何らかのプラスの影響があったらよいなあと思った、そんな合宿。言葉で伝えられることなんて、音で伝わることのほんの表層だけな気がする\n\u0026mdash; くろい (@kurokobo) August 5, 2012 OB って何なんだろう 歳が離れてくるといろいろと思うところが増える。\n老害にならないための確実な方法は、『現役に関わらない』しかないです(\u0026#39;ω\u0026#39; ) 関わる以上、程度の差はあれど確実に \u0026quot;歓迎されない\u0026quot; 部分があるはずだという事実は覚悟しないといけないですね(\u0026#39;ω\u0026#39; )\n\u0026mdash; くろい (@kurokobo) May 22, 2013 自分が現役に必要とされているかいないかという自問もけっこうだけど、それ以前にそもそも、必要とされる可能性がある程度に現役に自分が知られているのだろうかということを考えるべき(\u0026#39;ω\u0026#39; ) 自分が何をできて何を教えられてどこで頼れるのか、そもそも現役は知らないよ(\u0026#39;ω\u0026#39; )\n\u0026mdash; くろい (@kurokobo) May 22, 2013 現役さんが何か困ったときまず頼るのは誰かって、いきなり OB であるはずがないのよ(\u0026#39;ω\u0026#39; ) まずは友人だし先輩だし家族先生後輩だ(\u0026#39;ω\u0026#39; ) 現役にはそういう強力な理解者が居るのに、それをおさえて出ていってまで、相応の利益を彼らに与えられるという自信があるのかね(\u0026#39;ω\u0026#39; )\n\u0026mdash; くろい (@kurokobo) May 22, 2013 長く生きてる分だけ多くのことを知ってることは当然のことで、でもだからといって同じ空間に立ってるだけで知識欲旺盛に寄ってきてくれるわけではないのが現実、そもそも知ってること自体を知られてないし、最初に寄っていくのはやっぱり得体の知れない変なのよりはもっと身近なひとなわけで\n\u0026mdash; くろい (@kurokobo) August 5, 2013 高校時代や大学時代に高校生と一緒にいて『先生みたい』っていわれてもまだ学生ですっていえたからよかったんだけど、ほんとうに先生でも何もおかしくない年齢になってそれをいわれるとすこしふしぎな感覚になるということが最近分かった\n\u0026mdash; くろい (@kurokobo) April 2, 2013 かつてぼくが、ある OB に対して抱いた強い不満。この辺は 別のエントリ にも書いた。\nせめて部活はOBのものじゃないんですよ、ということだけでも言っておけばよかったかな。通じるとも思えないけれど。理不尽なことは往々にしてあるものですね\n\u0026mdash; くろい (@kurokobo) September 11, 2011 OBってなんだろうなーってずっと考えてるけどよくわからなくてもやもやしてる。演奏会って現役のためのものなのになんでOBが好き勝手コントロールしようとするの(・ω・) ただの暴慢でしょそんなの(・ω・) 勝手に話進めてあとは従わせようとかそういう思考にはまるで共感できない\n\u0026mdash; くろい (@kurokobo) September 11, 2011 OBがOBっていう地位にあぐらかいてさあお前らあがめたてまつれ奉仕しろ、みたいな姿勢は本当に気に入らない。自分は何もしない徹底的な受け身なのに『現役が○○してくれない、△△をよこさない』とか、現役はOB中心で考えてくれて当たり前、みたいなの、本当に嫌だ\n\u0026mdash; くろい (@kurokobo) September 11, 2011 現役が90人近くいて、資金難なわけでもないのに、わざわざ現役の手を煩わせてまでOB合奏をやろうとして、それで現役にデメリットを上回るだけのメリットを与えられるわけ？ OBの欲に現役を付き合わせるだけの意味があるわけ？ 『やりたいからやる』はまず現役のそれを優先させるべきでしょ\n\u0026mdash; くろい (@kurokobo) September 11, 2011 『現役から連絡来ないから、ああOB要らないんだなって思った』って、そりゃまさにその通りに必要とされてないから連絡こないだけなんだろ☆ とか思っちゃうよね。実際OB入れてやるより現役だけでやったほうがいいっていう判断があったわけだし、なんていうか、もう文句言うな☆\n\u0026mdash; くろい (@kurokobo) September 11, 2011 むかし話。\n『うるさいことをいう怖い先輩は一人くらいいないといけない』というよくわからない姿勢で現役の粗探しをしてリハーサルの時に好き放題わめくおもしろーい先輩もいましたね。なつかしいなー\n\u0026mdash; くろい (@kurokobo) August 28, 2010 OBに対して『話長いから短くしろ』とか何らかの要求をできる現役さんは少ないわけで、だから『だめっていわれないからやっていい』理論をOBが持ち込むとただひたすら迷惑なだけなわけで\n\u0026mdash; くろい (@kurokobo) August 28, 2010 当時の『OB』に対して思うところはたくさんあったけど、今は逆に思われる立場になっちゃったわけで、振る舞いとか距離のとり方とかいろいろなかなか思うようにいかないもんだなーとは思ってるんですよ\n\u0026mdash; くろい (@kurokobo) August 28, 2010 曲選び @doraaki0 そんな馬鹿な話あるかい(´ω`;) 『やりたい曲をやる』っていうのが大前提ですよ。楽譜が無いってなって初めて、誰かに頼むか曲を変えるか考えればいいの。ぼくはGWなら時間空いてますよ(・ω・)\n\u0026mdash; くろい (@kurokobo) April 28, 2011 @doraaki0 あのね、部活って誰がなんといおうと現役さんのものなんだから。決定権はすべて現役が持っていいんです。『先輩』はその決定の遂行のための、ただの『便利なツール』、道具って考えてもいい。やりたいようにやって、自分たちでできないところをヘルプだせばいいの(´ω`)\n\u0026mdash; くろい (@kurokobo) April 28, 2011 一年生と二年生 これもちょこっと 別のエントリ に書いてる。\n一年生も二年生も同じ舞台に立って同じ時間で同じ曲を弾く以上演奏者としての立場は対等なはずで、上意下達なだけの空気なのはわりと馬鹿げてるし、二年生が一年生にとって『万能』である必要もそうなろうとする必要もそうであるかのように振る舞う必要もないんだよ、という話がしたかった\n\u0026mdash; くろい (@kurokobo) August 7, 2012 パート決め。事実ですが、事実ですね。\nパート決めか(\u0026#39;ω\u0026#39; ) どうせ二年後には全員楽しそうに笑ってるってぼく知ってるよ(\u0026#39;ω\u0026#39; )\n\u0026mdash; くろい (@kurokobo) April 30, 2013 とはいうものの、経験則からの『やればどこでも楽しいよ』という発言は、経験のない一年生にとってはじつに無益な言葉である。提示すべきなのは判断材料であって、妥協理由ではない\n\u0026mdash; くろい (@kurokobo) April 30, 2013 そんなこんなで、Twilog にはぼくの 2009 年 6 月以降のついーとが全部残っている。Twitter をはじめたのは 2008 年 2 月だから最初のほうのは欠損してるけど、それでももうだいぶながくやってるなあと。関連するっぽいキーワードで検索していくつか抜き出してみると、自分のついーとながら考え方の変遷が見えてなかなかにおもしろい。\n黒歴史っぽくて消したいものもいくつかあるけど、なるべく残しておきたい。消すのはいつでもできる。\n","date":"2013-08-05T13:42:02Z","image":"/archives/884/img/DSC04864.jpg","permalink":"/archives/884/","title":"ギターと部活、音楽に関わるぼくの頭の中、あるいは感覚"},{"content":"ちょっとしたトラブルでわりと派手に穴をあけた。左のおしりのところ。\nズボッといったよねズボンだけに。ごめんなさいなんでもないです。\nそんなわけでどうしようもないので修理に出した。二年前のモデルなので、もう同じのを新しく買うってのができなかったのです。\nで、多少跡は残るかもとあらかじめ言われてはいたけど、仕上がりをみたら覚悟していたほどの残り方ではなくて、おおむね満足な仕上がり。\nじろじろ見られるとバレるけど、普通に着ている分にはわからなさげ。\n修理代は 17,000 円ほど。かけはぎで、面積に応じてお値段は上がっていくとのこと。納期は二週間くらいだった。\nちなみにこのズボン自体は 20,000 円くらいで買ったもの。見積もりの値段みてちょっと迷ったけど、まあいいかなって。\n","date":"2013-07-21T09:39:36Z","image":"/archives/866/img/DSC00426.jpg","permalink":"/archives/866/","title":"スーツのズボンに穴をあけたので修理してもらった"},{"content":"高校のころ所属していた部活 の古い録音の山が、講師の先生のところに眠っているらしい。何年も前にそんな話を聴いてから、いつか全部きちんとデジタル化して後世に残していきたいなあとながいこと思っていたのだけれど、最近ようやくこれに着手した。\n部活の公式 Web ページの中の “ギタサン史” によれば、ぼくらのルーツは 1972 年の秋に発足した同好会。今が 2013 年だから、もう 41 年を数えることになる。ぼくの生まれが 1987 年だから、それよりもずっと前。\n40 年を越える歴史があるというのに、さて、ぼくらが “過去の演奏” を聴こうと思ったときに聴けるのは、せいぜいここ 10 年分くらい。最近の部員の中では比較的長くぎたさんに関わっていると思われるぼくでさえ、手元にきちんとした形で持っているのは 2001 年度以降のデータだけ。\n世の中に録音が存在しないなら別だけど、物理的に残っているのであれば、それが聴けなくなる前に、聴ける状態にしておきたかった。\n経年劣化は避けられない。はやくどうにかしないといけない、このまま何もしないでいたら、ぼくらは膨大な何かを失うことになると、そういうぼんやりとした危機感があった。\n残っている記録媒体は、たいがいは何らかのテープ。危機感の根源は、テープの寿命ではなく、テープを再生する機械の寿命である。\nテープは強い。コンパクトカセットだろうが DAT だろうが miniDV だろうが Hi8 だろうが VHS だろうが VHS-C だろうが beta だろうが、とにかくテープは強い。信じられないくらいに強い。耐久性でいえば CD-R や DVD-R や HDD よりも何倍も何十倍も強い。今までの数十年も平気で品質を保ってきたし、多分この先の数十年も同じように品質を保ってくれるはず。\nだから別に今やらなくてもいいのではないかと思わないこともないのだけど、現実的にはたぶん、テープより先にそのテープを再生できる機械がなくなる。かつて一世を風靡したカセットデッキも、もはやまともな新品は TEAC のくらいしか手に入らない。DAT デッキなんてもうどこも作っていない。\n今あるデッキだっていつかは壊れるし、ガタがくるとテープが入ったまま取り出せなくなるとか中で絡まるとか切れるとか、そんなことも起こりうる。そんな現実をよそに、メーカの保守パーツの保管期限もどんどん切れてくるから、オーバホールはおろか修理さえも年々難しくなってくる。\nだからやっぱり、今、できるひとがやっておかなければいけない気がする。\n第一に、テープの中のデータを保全すること。第二に、保全したデータをリマスタリングして再利用性を高めること。第三以降は、考えていない。\n何年かかるかわからない。まずは音から。そのあとは映像。終わったら部活ではない別の団体の分。なんせ 500 本ちかくある。先は長い。いまは 50 本くらい終わったところ。\nそんなわけで、最近はレガシィな記録媒体と仲良く暮らしている。ぼくが生まれる前に記録されたメタルテープ ((コンパクトカセットは磁性体の違いでノーマルとハイポジション（クロム）とフェリクロムとメタルがあって、メタルがいちばん音がよい。家電量販店で今でもたまにコンパクトカセットを見るけど、もう劣悪なノーマルしか置いていない)) が想像をはるかに上回る音のよさでびっくりしたりとか、180 分の DAT をデッキに入れるときにちょっと緊張したり ((180 分の DAT はほかの時間のよりもテープそのものが薄いので、デッキの中で絡まったり切れたりしやすい)) とか、そんな毎日。\nしかしこういうことをやっていると、五十年後にこのデータは誰がどう管理しているんだろうなあとか、そもそも五十年後って部活どうなってるのかなあとか、高校っていうシステムがその時まで残っているのかなあとか、いろいろ考えてしまいますね。\nはてさて 75 歳のぼくは何を思うのでしょう。そもそも生きているかもわからない。\n","date":"2013-06-29T17:10:47Z","image":"/archives/818/img/DSC00329.jpg","permalink":"/archives/818/","title":"部活の古い録音、レガシィな記録媒体のお話"},{"content":"電子書籍系エントリの続き。\n自分で裁断するのはたいへんそうだったから “時間と手間はカネで買え” を合言葉に裁断代行業者さんに本を送ったら、たいへんよい品質で満足できた、というお話。\nまとめると、\n今回は カットブックプロ さんに頼んだ 99 冊裁断してもらったら、送料等含めて 1 冊あたり 118 円だった きれいに裁断されていた。でも 20 冊に 1 冊くらいはページ同士がくっついてる子がまぎれてた ある程度のまとまった冊数を裁断したいなら業者さんは大いに活用するべき という感じでした。\n業者さんを選ぶ 今回は カットブックプロ さんにした。“裁断代行” でぐぐったらいちばん上だったし、GW 中でも対応しているっぽいことが理由。値段でいえば 裁断ブックマート さんのほうが安そうだったんだけど、GW 中は休みっていうから、多少高くても対応が早いほうがいいなあということで。\n注文する 料金表 をみると、“文庫・コミック” と “全種類” とで二種類ある様子。今回は雑誌も大型本もハードカバーもいろいろあったので全種類のほう。\n今回は 99 冊 ((前のエントリでは 130 冊って書いたけど、数え間違えてた……)) の裁断をお願いする。100 冊パックを注文して箱を分ければよいのかとも思ったんだけど、FAQ をみたら “50 冊パックで 2 箱になった” に対して “冊数分に合ったパックに変更させてもらう” っていう感じで書いてあった ((“パック変更か送料安い方かに変更させてもらう” って記述もあってどっちか迷った（同じ Q が二個あるのはイケてないよね）んだけど、モメるのも嫌だからまあ分けておくか、くらいの判断)) ので、箱詰めのことを考えて、\n20 冊 + 30 冊 + 50 冊 で注文した。\nトップページのお知らせを見たら “新生活応援キャンペーン” とやらでクーポンコードが書いてあったのでこれも使う。3,000 円以上で使えるってことだったので、20 冊 ＋ 30 冊の注文と 50 冊の注文を分けて、それぞれでクーポンコードを使った。全部で 300 円引き。われながらせこい。\n注文したら振り込む。インタネットで振り込めば家から出ないで済む。楽。\n箱詰めして送る 適当な段ボール箱に詰める。気をつけたいことは以下。\n往復に耐えられそうな強度のにする（送った箱でそのまま返送されてくる） ひと箱の三辺の合計が 160 cm 以下にする（集荷してもらえない） ひと箱で 30 kg 以下にする（集荷してもらえない。できれば 25 kg にした方が安いらしい） 注文したパック単位で箱詰めする（今回は先述の注文だったので 20 冊 ＋ 30 冊 + 49 冊で詰めた） 詰めたら郵便局にゆうパックの集荷以来を出して、取りに来てもらって、往路分の送料を払う（復路分はパック料金に含まれる）。持ち込みのほうが安いらしいけど、重いから運ぶのたいへんだし、家から出たくないし、来てもらうに限る。\nちなみに本のソフトカバー、着けたままでもよい（業者さん側でちゃんと外して裁断したあと一緒に返送してくれる）らしいけどなんとなく外しておいた ((後の手間を減らすためにカバーだけでも先にスキャンしておく作戦)) 。附録の CD 類も抜いておいた。\n戻ってくるまで 一日目の夕方に注文、振込。夜に集荷。\n二日目の朝、入金を確認した旨の連絡。\n三日目の朝、本が届いて裁断作業に入る旨の連絡。夕方には裁断が終了して発送した旨の連絡が入った。予想以上に早い。\n四日目の昼、裁断済みの本が届いた。\n全体的に早かった。\nできあがり具合 箱を開けると、数十冊ごとにポリ袋でくるまれて、隙間には新聞紙が詰められた状態。\n裁断面はすごくきれい。\n一冊ごとに輪ゴムでまとめられている。輪ゴム自体のかかりは弱いから、うっかり山を崩すとたいへんなことになりそうな気はした。丁寧に扱えば問題なし。\n垂直にきれいに裁断されているので、基本的にはそのままスキャナに放り込める。基本的にはって書いたのは 20 冊に 1 冊くらいはページ同士がくっついてる子がまぎれてたからで、だからスキャナに放り込む前に念のため分離の確認はした方がよさそう。\n糊の付き方なんて本によって全然違うし、この値段で完璧な分離まで求めるのは酷な気がする。20 冊に 1 冊くらいなら許容範囲内だと思う。\nかかったお金 こんな感じ。往路分の送料含めて、総額で 11,720 円。99 冊だから、1 冊あたり 118 円。\n摘要 金額 備考 注文 1 4,435 円 20 冊 ＋ 30 冊パック、割引適用後 注文 2 3,635 円 50 冊パック、割引適用後 送料 3,650 円 3 箱分、往路分。復路分はパック料金に含まれる 合計 11,720 円 1 冊あたり 118 円 おわりに 1 冊あたり約 120 円というのを高いとみるか安いとみるかはひとそれぞれだろうけど、個人的にはこの値段なら気軽にできてすごくよいと思った。\n自分でカッタでやるというのは論外だし、裁断機（40,000 円くらい）を買うなら単純計算で 340 冊くらい切らないと元が取れないし。\n340 冊ってすぐじゃんって思うけど、裁断厚が 18 mm までっていうから大型本はつらい（今回送ったのはほとんど 20 mm 以上だと思う）し、そうでなくたって 340 冊も自分が手を動かしてやるっていうのは根本的にすごく手間だし。\n注文や箱詰めの作業分 “だけ” の手間と時間でこの品質の裁断結果が買えるなら、充分アリ。\n文庫とコミックだけならもっと安いし、もっと大きな単位のパックで注文できればさらに安くできるし。1 冊だけ裁断したい、みたいなときは小回り利かないけど、ある程度冊数があるなら業者さんに頼んでしまうのがぼくの判断基準ではいちばんいいと思った。\n関連エントリ 前 TLX-TAB7S にぴったりなケースを手に入れた（ただしカメラは死ぬ） TLX-TAB7S を買って電子書籍生活を始めた ScanSnap iX500 を買って自炊生活を始めた ","date":"2013-05-26T06:03:32Z","image":"/archives/763/img/DSC09192.jpg","permalink":"/archives/763/","title":"裁断業者さんに本の裁断を頼んだら実質 1 冊 118 円だった話"},{"content":"はじめに アニメ『進撃の巨人』のオープニングのパロディが動画サイトでいろいろと出回っている。\nで、そういう動画を観ると、途中の『進む意思を嗤う豚よ』以降のところ、作者さんごとに思い思いの書体で再現されておられる。じゃあせっかくなんだし、なるべくオリジナルに忠実にしたらどうなるんだろう、ということで調べた。\nまとめると、\n使われている書体はダイナフォントの『DF 華康明朝体 W5』と『DF 極太明朝体』の二種類 前半は装飾なし、後半はシャドウやら光彩やらの装飾がある という感じ。\n厳密には DF か DFP か DFG かが判断できないので、以下は DF または DFP と仮定 して説明する（詳細は追記部分参照）。\nサイズや文字間隔、配置などは記録するのがめんどくさいのでトレスでどうぞ。\n前半（進む意思を嗤う豚） 前半は漢字も仮名も全部、DF 華康明朝体 の W5。特に装飾はしないで、背景の上に文字を乗せているだけ。\nさて、最初の『進』。文字サイズ二種類で、ツメもなにもいじらずにそのまま配置されている。\n次の『進む』は、文字サイズは三種類。“進む” にツメあり。\n次は最初の『進』の画面と同じサイズ。\n次はサイズは三種類。“意思を” も “いし” もツメあり。“いし” はけっこうツメられてた。\n『進』や『意』と同じかと思いきや、“わら” が若干大きい？ そしてちょっとだけツメあり。\n次、“わら” はがっつりツメられてる。サイズは三種類。\n次は『嗤』と同じサイズ。“ぶた” はちょいツメ。\n最後、“ぶた” がちょっとツメあり。“豚よ” はサイズの違いだけ。\nここまでは、書体とサイズと配置（ツメ含む）さえ合わせればあまりひねりは要らなさげ。\n後半（家畜の安寧） ここからは全部、DF 極太明朝体 になる。かつ、装飾つき。\n前半は文字単体がばばんと出てきて、そのせいか歌詞として（言葉として）のつながりは見えにくかったんだけど、ここからは文字よりも歌詞の一節をそのまま出してくる。文字が急に太くなることもあって、だいぶ印象は変わる。\n最初、『家畜の安寧』は、\n漢字は “家畜” より “安寧” がごくごくわずかに小さい。仮名はだいぶ小さく ツメなし ドロップシャドウを斜めに二方向、距離は近く。文字を縁取る感じで やや広めの光彩（外側） の二つだとそれっぽくなった。\n次の『虚偽の繁栄』は、\n漢字は大きく、仮名は思い切り小さく ツメなし ドロップシャドウを斜めに二方向。光源が左上のほう（右下方向への影）が距離が遠い うすい光彩（外側） でそれっぽい感じに。\n次、『死せる餓狼の』（この前に “死せる” だけのコマがあるけど同じっぽかったのでまとめてる）は、\n漢字は “餓狼” より “死” がごくごくわずかに大きい 仮名は漢字より小さく。“の” より “せる” がわずかに大きい ツメなし 光源を右上にしたドロップシャドウ。距離遠め、ちょっとだけ薄め はっきりめで狭めの光彩（外側） だと似てる。\n最後、『自由を！』は、\n“自由” と “！” は同じサイズ、“を” だけ小さく ツメなし 光源を右上にしたドロップシャドウ。距離遠く、薄く 超うっすらで見えないくらいの光彩（外側）？ かなあというところ。\n余談だけど個人的には、“を” と “！” の間をもうちょっとつめ（“を” の文字送りをいじっ）たほうがいいのではないかと思った。\nさいごに ぼくは制作陣ではないのでこれが正しいという保証はまったくできないけど、それっぽい感じの再現ができたのでよかった。\nぼく自身はべつにパロディ動画作る気はさらさらない。なぜこういうことをするかといえば単純に『文字を使ってプロの手で表現されたもの』を『同じ書体でトレスする』のが楽しくて好きだからで、昔からこういうことをしてよく遊んでいた。\nトレスすると、特にロゴとか見出しとかの目立つものには、文字をそのままべたっと並べてるところなんてほとんどないということが分かる。一文字一文字、全部間隔やら位置やらサイズやらが微調整されている。制作側のこだわりがわかっておもしろい。\n見て “違和感のないロゴ” というのは、一見シンプルで真似しやすそうに見えて、実はものすごく作り込まれているもの。ロゴに限らず組版でも文字組みでも何でも、作り手がこだわればこだわるほど、作り込めば作り込むほど、受け取り手にとっての違和感はなくなって “気がつかれなく” なる。トレスを通じてそういうところに気がつけて、製作者の意思に触れられるのは、なかなかに快感で楽しい。\n自分が何かをデザインするときにもそういう “気がつかれないようなこだわり” はけっこうしていて、それで褒められるとうれしいもの。“気がつかれなかったらぼくの勝ち” みたいな、誰にも言わないそういうこっそりとした遊びをすることもたまに。\nぼくはプロのデザイナでも DTPer でも同人作家でも何でもないからもちろん詳しいことはよくわからないんだけど、それでもなんとなくフォントが好きで、いろいろと手を出してしまうのでした。\n追記（2013/05/19 23:00） ドロップシャドウぼかしてあるなあと思ってたんだけど、参考にしていた動画ファイルの画質が悪かった（ポータブル用に圧縮したやつ）だけっぽい。圧縮前のほうをみたらきれいにパキッとしたドロップシャドウだった。\nというわけで “家畜” 以降の画像と説明文を差し替えた。ソースの画質だいじですね。ごめんなさい。\n追記（2013/05/26 12:00） DF と DFP と DFG のどれを使うべきか明記していなかったので冒頭部分に追記。\nこの判断には “アルファベットが等幅か否か” と “ひらがなが等幅か否か” の情報が必要なんだけど、アルファベットは出てこないし、ひらがなは等幅でぴったりなところとプロポーショナルでぴったりなところが混在している状態（どっちでもぴったりではないところも当然ある）で、判断できない。\n判断できないなら仮定するしかないので、『画面ごとに “どっちが近い” というのはあるけど結局ツメやアキの微調整は必要』『画面に応じてフォントを変えるのは煩雑』『どれを選んでも漢字とひらがなの字形は変わらない』というのを理由に、このエントリでは等幅（DF または DFP）を基準にした。\n","date":"2013-05-18T02:15:00Z","image":"/archives/711/img/01.png","permalink":"/archives/711/","title":"進撃の書体、オープニングを真似るためのフォントの話"},{"content":"大学生ふたりに写真を撮りに行こうと誘われたので行ってきた。\nふたりは一眼レフ持ちで、何ミリのレンズがどうのこうのときゃっきゃしてる系の方々なので、一緒にいくのがミラーレス一眼と付属キットのレンズしか持っていないぼくでよいのかしら感はあったのだけれど。でも行ってよかった、楽しかった。\n写真を撮りに行くと言われてついていったはずが、到着してまず何をしたかといえば川下りなのがおもしろい。思っていたより行動派だった。\nいちばん長いコース、6 km の川下り。ラフティングではないし風もゆるやかで、連日の快晴のため波もない。穏やかそのもの。\nこういう穏やかな流れを “瀞” といい、“瀞” な区間が “長い” から “長瀞”、というらしい。船頭のにいちゃんが言っていたのでたぶん正しい。\n船を降りて町にでると、下町感あふれる空気。\nそして名所らしい “岩畳” というものを観に行く。そもそも恥ずかしながら最初 “長瀞” という地名が読めなかったぼくが、“岩畳” なんてものを知っているわけがない。\n開放感が気持ちよかった。視界が広いのは爽快だ。岩の価値はわからないけれど、積層感のあるごつごつした力強さは迫力がある。町の近くはひとがけっこういたけれど、ずいずい奥に進めば静かなもの。\n観光地だから管理上仕方のないことだろうとは思うのだけれど、一部コンクリートで岩の隙間を埋めて足場にしてあったのは、少し残念。\n岩畳から 15 分くらい歩くと小さな山があって、ロープウェイで登れる。頂上にはこれまた小さな動物園があったのだけれど、動物にシャワーを浴びせたくなることしきり。\nとはいえ、平和そのものだ。特に、豚が死んだように日向に転がって寝ていたあたり。\n夕方になって、閉店間際のお店で肉汁うどんなるものをいただく。肉だった。\nおわりに 写真を載せたときにどういうことを書けばいいのかわからない。カメラは NEX-5N、レンズは付属のズームレンズ。設定は基本オート。RAW で撮った。本当は写真ごとに数字をいろいろと書くべきな気はするけれども、よくわかっていないので書けない。EXIF は現像のときに消してある。\n帰宅してそのままの勢いで現像して facebook に投げたのだけれど、いまあらためて観るとどう考えてもコントラストと彩度を上げすぎている。とはいうものの編集しなおすのも悔しいので、このエントリの画像は facebook に上げた中からそのまま抜粋している。もう少し穏やかな画にする術は身につけたいところ。\nまわりにカメラ好きが多いので、困ったらいろいろ聴くと聴いた以上に教えてもらえるのだけれど、平気で 20 万円などというレンズをお薦めしてくるから油断できない。\nレンズ、一本買ったらあとは泥沼にはまりこむだろうことが（自分の性格上）ほぼ確実なので、だからなるべく手は出したくないのだけれど。しかしまずはこれを買えばいいよと薦められたレンズが 2 万円程度という気軽にぽちれるお値段だったので、そろそろ危ない状態になりつつある。\n旅行記めいたものを初めて書いたけれど、形で残るのはなかなかにおもしろい。実は日ごろ、外に向けて何かを書くときは常に、黒歴史にならないといいなあと期待している。はてさてどうなることやら。\n","date":"2013-05-10T11:29:00Z","image":"/archives/671/img/130506-120152_DSC09914.jpg","permalink":"/archives/671/","title":"長瀞に行ってきた"},{"content":"前の『TLX-TAB7S を買って電子書籍生活を始めた』の続き。\n前のエントリではこんなことを書いた。\nケースと覗き見防止シートが欲しいんだけど、選択肢は、\nボタンや端子やカメラが埋まるのは諦めて、汎用品や Nexus 7 用のを使う うまい位置に穴があいているケースを探す ケースなしで過ごす というあたりですね。スマートフォンは諸々の事情でケース無しで使う派だけど、このサイズのタブレットだとケースが欲しいから 3 は無いなあ。\nそんな状態でヨドバシカメラに行ったら、ブライトンネットさんの BM-NE7FLSTD/BK っていうちょうどよいのがあった。\nNexus 7 用のケース レザー生地であんまり安っぽくない 裏側にハンドベルトが付いているので片手で持つときも安心 横置きのスタンドにもなる（角度は二段階） っていうやつで、TLX-TAB7S につけた場合は\n端子類は隠れないでそのまま使える microSD カードスロットがうまいこと保護される カメラは半分隠れて使いものにならなくなる マグネットスリープは使えない という結果に。特に不満のない状態。\nつけたところ 基本的な使い心地とかはぐぐったら出るレビューエントリなどを観てもらうとして、本来 Nexus 7 用のケースを TLX-TAB7S につけるとこうなるよ、というところを。\nもともと TLX-TAB7S 自体が Nexus 7 とよく似ているので、見た目の違和感は特にない。うまいのが右下のツメの部分で、これがうまいこと microSD カードスロットをぴったりと隠してくれる。\n最低限のツメでしか本体を固定していないので、イヤホン端子も USB 端子も、本体横の電源ボタンも問題なく使える。ケースをつけたまま充電できる状態を保てたのはうれしい。\nただし、もともと Nexus 7 でスピーカがついていた位置のスリットはただの謎の隙間になる。そしてカメラは半分隠れるので、実質使いものにならなくなる。\nほかの選択肢 Amazon で “Nexus 7 ケース” などと検索すると、形がこれによく似たほかのメーカのケースがけっこうある。ツメの位置も似ているので、こういうほかのメーカのでもきっとうまいこと使えると思う。\nちなみにヨドバシカメラでは、店員さんに『Nexus 7 とほぼ同じサイズのタブレット端末を買ったが専用のケースがないのでほかのを流用したくて探している』ことと、『流用した場合のボタンや端子の埋まり具合も気になるのでできれば試着して具合をみたい』ことを伝えたら、快く応じていただけた。売り物を開封してまで試着させてもらえたのでとてもよかった。\n念のため書いておくけど、こういうツメで固定するタイプのがこれしか置いてなかったので問答無用でこれに決定しただけで、だから Amazon にあるような似た製品と比べてあえてコレだというわけではない。たまたま。\n覗き見防止シート これもヨドバシカメラで相談に乗ってもらったんだけど、そもそも世の中に 7 インチ用の覗き見防止シートが存在しない様子。Galaxy TAB 用のとか iPad 用のを切るにしても、店頭在庫がないそうで。ひとまず普通の保護シートを買って帰った。\n後日 Amazon で調べたら、ちゃんとしたやつは 5,000 円くらいするし、そこまでして要らないかなあという感じがしたので、結局まだ買っていない状態。保護シートを適当な大きさに切って貼って使っている（上の写真）。\n関連エントリ 前 ScanSnap iX500 を買って自炊生活を始めた TLX-TAB7S を買って電子書籍生活を始めた 裁断業者さんに本の裁断を頼んだら実質 1 冊 118 円だった話 ","date":"2013-05-08T14:16:58Z","image":"/archives/650/img/DSC09193.jpg","permalink":"/archives/650/","title":"TLX-TAB7S にぴったりなケースを手に入れた（ただしカメラは死ぬ）"},{"content":"前の『ScanSnap iX500 を買って自炊生活を始めた』の続きというか関連。\n自炊するならその電子データを読める端末が欲しいよね、ということで買ったのが TruLuX さんの TLX-TAB7S。\nいきなりまとめると、\n価格もサイズも解像度も Google の Nexus 7 の対抗馬っぽい位置づけ microSD カードスロットがあってうれしい SIM カードは入らない 専用アクセサリが全然発売されていなくて困る な、感じ。ちなみに Nexus 7 は持っていないので公称値やらほかのひとのレビュー記事やらを参考に書いています。\n見つけてから買うまで そもそもそんなに高級なのは要らないと思っていたので、予算はだいたい 2 万円前後。言わずと知れた Nexus 7 が筆頭候補だったんだけど、価格.com のランキングを眺めていると似たような値段とサイズの TLX-TAB7S とかいうのがあるらしいとわかった。\n失礼ながら見たことも聴いたこともないメーカだから若干不安になりつつも、調べると公称スペックは悪くなさそう。Web の情報だといわゆる “中華パッド” なる表記も見られたけど、調べたら日本の会社だったしまあいいかなと。なにより Nexus 7 にはない microSD カードスロットの存在は大きかった。\n使い心地 いくつか写真を載せる。見た目は正直だいぶ安っぽい。とくに端子周り。まさか microSD カードスロットが外から丸見えとは思わなかった……。好意的に解釈すれば抜き差しがしやすいということにはなる。\nとはいえ、自炊 PDF ファイル（300 dpi、Acrobat で OCR 済み。ClearScan ではない）を読んでみたら、ページめくりのストレスもないし至極快適。凝ったことをやるつもりもないからこれで充分だなあというところ。\n無線 LAN につないで自宅の NAS に突っ込んであるアニメもさくさく観られたし、特に不満はない状態。\n問題点 専用アクセサリが全然発売されていないのがつらい。\nサイズは Nexus 7 と高さも幅も厚さも 1 mm 以下の違いしかないから、ケースは流用できそう…… と思いきや、端子とかボタンの位置が全然違って困る。\nてきとうに絵にしたらこんなん。青が Nexus 7 で、赤が TLX-TAB7S。何ひとつとして同じ位置にない。\nこの手のタブレット用のケースってボタンが押せるように穴があいているものが多いと思うんだけど、だから Nexus 7 用のケースを買うとまず間違いなく埋まる。端子類もたぶん同じ状態になる。\nケースと覗き見防止シートが欲しいんだけど、選択肢は、\nボタンや端子やカメラが埋まるのは諦めて、汎用品や Nexus 7 用のを使う うまい位置に穴があいているケースを探す ケースなしで過ごす というあたりですね。スマートフォンは諸々の事情でケース無しで使う派だけど、このサイズのタブレットだとケースが欲しいから 3 は無いなあ。\nぼくの場合は幸い使い方が限定されているから、画面ロックはアプリケーションで制御すればいいし、ボリュームは常時オフで固定でいいし、カメラは使わないし、だからまあ埋まってもいいかなって気はしている。外で動画見るとなるとヘッドホンだけが問題だけど、最悪自分で穴あければいいわね。\nそんなこんなで電車で電子書籍を読む生活を始めます。\n関連エントリ 前 ScanSnap iX500 を買って自炊生活を始めた 続き TLX-TAB7S にぴったりなケースを手に入れた（ただしカメラは死ぬ） 裁断業者さんに本の裁断を頼んだら実質 1 冊 118 円だった話 ","date":"2013-05-02T04:03:23Z","image":"/archives/631/img/DSC09186.jpg","permalink":"/archives/631/","title":"TLX-TAB7S を買って電子書籍生活を始めた"},{"content":"はじめに 本職が IT 屋さんだからといって何でもかんでもデジタルが好きなわけではなくて、もともとぼくは “本を物理的にこわすのは抵抗がある” という立場のひとだった。まわりのひとからシートフィードスキャナを買ったという報告を聴いても、ああ買ったのそうなの、本こわしちゃうのね、というくらいにしか思えなかったくらい。だから積極的に自炊関連の情報を漁ることもなかった。\nでも部屋を片付けていて、\nいざというときにとにかく “読めれば” いいだけ 自分にとってはただの “情報源” だ 思い出程度に見た目が残っていればいい という書類やら本やらがけっこうあることに気がつく。例えば、\n古い研修資料 資格試験の勉強の本 古い手書きのノート いつか復習するかもしれない勉学の本 古い雑誌 などなど。\n要するに、“物理的な所有にこだわりがないもの” ってけっこうあるんだなあということで、なるほどこういうものをスキャンしてしまえばよいのかと、そして物理的なモノは捨ててすっきりしてしまえばよいのかと、自炊という言葉が一般化して長いこと経った今になってようやく納得がいった。これまでは “大事な蔵書をバラすなんて嫌だ” という感覚が捨てきれないでいたのだけど、“大事な蔵書しか家にないわけではない” というだけのことだった。\n購入まで いちど納得してしまえば話は早い。自分の持っている本や紙束を眺めて、以下のふたつに脳内で分類してみて、ああ 2 がすごく多いなあとわかったら、あとはぽちるだけ。\n物理的に持っておきたいもの 以外のもの モノをどれにするかは少し考えたけど、最初の一台だから、ド定番であることと Web 上の情報も多いこと、そして競合メーカの中では一番最近に発売された最新機種ということで、PFU さんの ScanSnap iX500 を選択。\n到着して設定してスキャンするまで 設定をどうするかは悩ましかったけど、富豪的思考で決めた。\nHDD はいくらでもあるからできあがりのファイルサイズは気にしない。なるべく綺麗にスキャンしてマスタデータとして保管しておけば、貧民的処理（サイズを減らしてモバイル端末のスペックでも閲覧しやすくするとか）は、あとからでもどうにでもなる。\n問題はむしろ物理的なほうで、裁断機がないこと。ひとまず使ってみてから考えよう的な思想でスキャナだけをぽちったので、製本されたのをスキャンするならカッタでせこせこやらないといけない。\nまあでもさしあたってスキャンしてしまいたいのは本よりも紙束だったし、スキャンしたい本はリング製本（海外のってなぜか多いよね）が多くて裁断しなくてもほぐせたし、ちょっとカッタで切るのも体験してみたいよね、という感じで、まずは平積みにすると 80 cm にもなろうかという山をやっつけることに。\n使ってみて ちょう便利。\n80 cm の山をさくっとやっつけるころには、“いつか見るかもしれないからちょっと場所とるけどとっておこう” だった思考が “いつか見るかもしれないけど場所とるからスキャンして捨てよう” に完全にシフト。\nそうするといよいよ製本された技術書やら参考書やら雑誌やらを処分したくなって、ためしに仕分けをすると 130 冊近くになった。ここで考えられる選択肢はこんな感じ。\nカッタで 130 冊がんばる（追加出費なし。膨大な時間と労力が必要） 裁断機を買って 130 冊がんばる（金銭負荷が大きい代わりに将来的な追加出費は抑えられる。そこそこの時間と労力が必要） 裁断業者さんに 130 冊送る（一冊 100 円未満くらいのコストでお手軽な代わりに、言うなれば従量課金。労力いらず） 合言葉は “時間と手間はカネで買え” である。3 に決定。だって手作業つかれるんだもん。そもそもこれは “バラすまで” の話で、このあとにスキャンする作業が等しく待っているわけだし。\nというわけで、裁断を申し込んで振り込んで、箱詰めして集荷をお願いして引き取ってもらったところまでが今日。戻ってきたらまたエントリ書きたい。\nこれからの話 大量の PDF ファイルができてくるわけだけど、じゃあ何か閲覧しやすいデバイスも欲しいよね、ということで、TruLuX さんの TLX-TAB7S を追加でぽちったところ。とくにこだわりはなかったんだけど、それなりの解像度で軽くて内蔵以外の記録媒体が使えて（この子は microSD カード）、あんまり高くないもの、という選び方。\nあとは富豪的思考で取込んだ大きな PDF ファイルをいかにダイエットさせるかというのも考えないといけない。この辺はタブレット端末が届いて実際に使ってみてから試行錯誤するところかなあと。他の端末向けの情報はぐぐるといろいろ出てくるから、いろいろやってみる。この辺もエントリ書きたい。\n関連エントリ 続き TLX-TAB7S を買って電子書籍生活を始めた TLX-TAB7S にぴったりなケースを手に入れた（ただしカメラは死ぬ） 裁断業者さんに本の裁断を頼んだら実質 1 冊 118 円だった話 ","date":"2013-04-29T12:46:23Z","image":"/archives/616/img/DSC09170.jpg","permalink":"/archives/616/","title":"ScanSnap iX500 を買って自炊生活を始めた"},{"content":"イヤホンのイヤチップ、軽視されがちだけど実はいろいろと種類がある。\n普通のシリコンのでも堅いのと柔らかいのがあるし、シリコンの中にスポンジが詰まってるのもあるし、シリコンじゃなくて耳栓みたいな低反発フォームでできたのもある。\nイヤチップを変えると物理的な着け心地も大きく変わるし、イヤホンがぽろりと抜け落ちやすくなるのも防げる。遮音性や音漏れの具合ももちろん変わる。キュッと詰めればとくに低音はしっかり聴こえるようになるけど、音像感は狭くなるとも言われる。\nで、SONY XBA-4SL を恥ずかしながら紛失してからというもの、SENNHEISER IE80 を愛用していたわけだけど、イヤチップを付属のや家にあるのをいろいろ付け替えてみてもどうにもしっくりこない。\nそんなわけで、かねてからウワサは聴いていた コンプライのフォームチップ か モンスターケーブルのジェルチップ あたりを試聴しつつ買ってみようということで、秋葉原のヘッドホンとイヤホンの専門店、e イヤホン さんに行ってきた。この手のパーツを 1 ペア単位でバラ売りしているお店はここ以外に知らない。\nこのお店、大量のイヤホンやヘッドホンを試聴できるし、イヤチップみたいなアクセサリ類も試せる。ヘッドホンアンプも大量にあるし、何より店員さんの知識量が膨大なので、イヤホンヘッドホン関連で迷ったらとりあえず行って話をしてみるのがおすすめ。\nさて、コンプライのもモンスターケーブルのも、どちらもサイズがいろいろある。で、店員さんとわちゃわちゃ相談しつつとっかえひっかえ試して、着けた瞬間キュポッといい感じにおさまったジェルチップの S サイズを 2 ペア購入。1 ペアで 580 円。コンプライとは迷ったけど、ちょっと硬くてぼくの耳だと安定感がそこまで得られなかった。\nベースはシリコンのふつうのやつなんだけど、隙間がジェル素材で満たされているから “ジェル” チップ。\n手触りはまるでグミ、ぷにぷにでかわいい。\n色が白しかないのが惜しいよね、まあいろいろサイズあるから区別するには仕方ないんだろうけど…… などと店員さんと話したけど、着けてみるとやっぱり白くて、イヤピース部分がすごく目立つ。\nだから黒がおおいぼくの持ち物の中でのこの白は逆に探しやすくていいのかもしれないと思うことにした。耳に付けたときのキュポッとしてプニッとくる感触がいい。けっこうしっかりした遮音性が確保できたから、電車の中でもいろいろ聴きやすい。\n次はポータブルヘッドホンアンプがほしい。でも今の DAP はライン出力ができないから、先に買い替えるべきなのはこっちかなあなどとも考え中。お金が……。\n追記（2013/03/24 23:30） ……と、まるでジェルチップの宣伝のようなエントリを書いたはいいものの、やっぱりコンプライが気になるのでこっそり買って使ってみたら、なんだか具合がすこぶるよいので結局こっちに落ち着きそう……。試聴したときの違和感は何だったんだろ。\nジェルチップ、おそらくぼくの内耳の形の所為で、顎の間接の動きによって特に右耳の聴こえ方が激しく変わってしまって、動き回りながら使うにはいまいち向かなかったみたい。内耳の形に追従してシリコンが変形してくれるのはいいんだけど、中で詰まって先端の穴が閉じちゃう（？）のかもなあなどと予想。XS（灰色）も買ってみたんだけどあまり改善しない気配で。\nコンプライは軸がしっかりしてるから穴が閉じようがないし、指でくしゅっとしてから詰めるともこっときてぴたっとなるのでよい感じです。とはいえコストパフォーマンスはよくない（数か月で交換するのが推奨されてる）ので、維持費がけっこうかかっちゃう。この点はやっぱりシリコン系が強いですね。\n関係ないけど、公式の対応表だと 500 系なのに、e イヤホンの対応表だと 400 系なのよね。e イヤホン信者（？）なので 400 系にしてみたけど特に不満ない感じ。右耳は S で良いんだけど左耳は M でもよかったかも。\nそんなわけで徐々に手持ちのイヤチップが（使わないのに）潤沢になってきた。モンスターケーブルのフォームの S、XS、ジェルの S、XS、コンプライの TX400 の S。ぼくが使わないのは布教用にします。\n","date":"2013-03-20T07:02:48Z","image":"/archives/580/img/DSC07799.jpg","permalink":"/archives/580/","title":"イヤホンのイヤチップを買い替えた話"},{"content":"いよいよ建て替え工事がはじまったらしい。\nそろそろはじまるとながいこと言われては来たものの、その気配もうかがえないまま数年。今まで校庭として使われていたところ──いつか校庭ではなくなるところ──に、やっと資材が置かれて、組み立てられて、そんなところからようやく進行中と聴く。\nそんな中、友人が、すてきな本をつくった。\n友人の校内のスケッチとともに綴られる、塚原先生の二十六篇のエッセイ。消えゆく校舎のひとつの遺し方。変化を前向きに受け入れることで新しく生まれる価値もある。\n2003 年、ぼくが多摩高校の 48 期生として入学してから、この四月で十年がたつ。いまでも数月に一度は高校に行っているから、姿を消しつつある母校をいまさら眺めたところで語るほどの懐かしさも感じないのだけれど、それでも少しばかりの寂しさはおぼえる。\nトイレが外にしかないような、21 世紀にもなって信じがたい環境、それでもよい校舎だったよねと、古い友人に会うたびに語られる学び舎。\nでも校舎への評価なんてものは、結局はそのまま自分の高校生活への評価だ。よい校舎だと今思えているのは当時その校舎で生活するのが楽しかったからで、だから友人が感慨深げによい校舎だったなあと語るのを聴くと、ああこのひとも高校生活が楽しかったんだなあと、他人のことながらうれしくなるものである。\n『多摩高校』というある種のシステムを形成する要素のうち、『校舎』のしめる割合はどのくらいのものだろう。いまのあそこから校舎 “だけ” が新しくなったとして、それ以外がどう変わるのか。勉強するだけなら校舎なんてどうでもよさそうだけど、知恵とか知識とかそんなくだらないものの伝達よりも “生き方” をつくる方が大事だと思っているので、その意味ではインフラストラクチャの更改はじんわりとした影響もたくさんあるだろうなあと。\nながく続くものが必ずしも最良かといえばぜんぜんそんなことはなくて、もっとよいものはたくさんあるはずなんだけど、そうかといって新しくしたら必ずもっとよくなるとも限らない。風邪をひいて薬を飲まされて、それでその薬が効いたかと聴かれても、飲んでいない場合と比較できないからわからないと、残念ながらそう答えるしかないのが現実。\n何十年もあと、あたらしい “校舎” を通じて思い出を語る彼らの顔が、さいこうに幸せそうで楽しそうだといいなあと、ぼんやりと思いつつ。\nページの中でゆるやかに描かれる高校にふれて、ようやくぼくも卒業できる気がした。\n追記（2013/03/12 00:00） エントリ公開後、本の画を担当した友人からこんな連絡をいただいた。\n@kurokobo ポストありがとう。よければこのリンクも貼って下さい。 http://t.co/w5ZdW4dhdv\n\u0026mdash; Tezzo SUZUKI (@TezzoSUZUKI) March 10, 2013 ポートフォリオとして携わった作品をまとめているようで。高校当時からぼくとはぜんぜんちがう目で世界を見ているなあという感覚はあったのだけれど、こうやって実際に制作物をみると本当にそう思う。そんなわけで、興味あればこちらもどうぞ。\n時分の庭 - a set on Flickr ","date":"2013-03-10T12:41:37Z","image":"/archives/547/img/DSC05654.jpg","permalink":"/archives/547/","title":"多摩高校"},{"content":"会社用のネックストラップを新調。\nメーカも型番も何もわからない、大学時代に CEATEC で貰ったノベルティのものを愛用していたのだけど、ちぎれかけてきたので本腰を入れてインタネット力（？）を駆使して型番を特定、3 つ注文して今日届いた。\n革製なうえめかめかしい金属感がたまらないかわいさの逸品。安いんだけどね。\n好きなものを着て会社に行くとか、業務用 PC に好きなアプリケーションを入れてカスタマイズするとか、好きなペンや好きな小物を使うとか、もちろん許される範囲内でではあるけど、好き勝手にやることでモチベーションが上がるなら、そういうことはどんどんやるべきだ、と。\nまわりの大勢のスーツのなか、ひとり詳しくは書けない程度に自由すぎる出で立ちをしたぼくの配属直後の上司はぼくにそう言い、そしてぼくはそれに多分に影響されている。\n異動になったいまでは顔を合わせることも少なくなったその上司、会うたびに『今日も黒いね』と声をかけてくるけど、たぶんぼくが平気な顔をして黒服で通勤しているのは、あなたがぼくにそう言ったからな気がします。\n","date":"2012-11-09T13:04:20Z","image":"/archives/533/img/DSC05157.jpg","permalink":"/archives/533/","title":"くびひも系会社員"},{"content":"前置 vSphere 環境をさわるとき、ふつうは vSphere Client でがちゃがちゃ操作する。そのうち慣れてくるとだんだん PowerCLI 環境に手を出すようになって、CLI 環境でいっぱいの VM をまとめて操作できてやばいこれちょう便利うひょーってなる。なるんだけど、それにも慣れると、もともと提供されてるコマンドレットって地味にかゆいところに手が届かないことにぼんやりと気付き始める。で、細かい操作をしようとすると、結局は定義済みのコマンドレットを卒業して、.NET インタフェイスをさわったり View オブジェクトをこねくりまわしたりしないといけないということを悟る。\nここまで来ると Web でぐぐって出てくる情報もだんだん減ってきて、vSphere Client ではすぐできるのに PowerCLI ではどう書けばいいのやらさっぱりだなあ、というところがぽろぽろ生まれてくる。\nある操作をするためのインタフェイスに GUI と CUI がある場合、（VMware に限らず）だいたいは CUI の方が原始的で、原始的だからこそ細かいところまで “なんでもできるはず” という幻想を抱かせてくれる。だから “GUI でできるのに CUI でできない” という状況に遭遇すると、GUI だって結局中で API 叩いてるんだろ、コード教えろやこのやろう、的な気持ちになること請け合い。どうにも納得できない。\nこのエントリで紹介する VMware Onyx は、まさにその『GUI ではできるのに CUI でどうやるのかわからない』を解決してくれるすばらしいツールなのでした。\n概要 ざっくりいうと、\nローカルプロキシとして動作して vSphere Client と vCenter Server（または ESXi）の間の通信をフックして vSphere Client での操作を PowerCLI や C# のコードとして吐き出してくれる というもの。この時点でもう便利そうな匂いがぷんぷんしてやばい。\nしかも Onyx たんは存外にお行儀がよくて、吐かれたコードそのままこぴぺでも充分実用的。この手のトレス用ツールってだいたい可読性の著しく低い下品なコードを垂れ流すイメージがあったんだけど、これはそんなことなかった。\nインストール ダウンロードして解凍するだけ。\nダウンロードは以下のどちらかから。本エントリ作成時点での最新バージョンは 2.1.4 で、バイナリは “Onyx_2.1.4226.28167.zip”。\nhttp://labs.vmware.com/flings/onyx http://communities.vmware.com/community/vmtn/server/vsphere/automationtools/onyx Windows XP 以前の環境（というか非 Unicode 環境？）だと、実行ファイルまでのパスに 2 バイト文字があると起動できなくなるっぽいので注意。\n使い方 起動すると出てくる Onyx のメイン画面で、左上の [Connect] ボタンを押下 接続先の vCenter Server（または ESXi）を入力。[Launch a client after connected] にチェックを入れて、vCenter Server（または ESXi）につなげるときの認証情報を入力 vSphere Client が起動したら、Onyx の画面で左上の [Start] ボタンを押下 vSphere Client でコード化したい操作を実行 Onyx の画面にコードが出力されることを確認 必要に応じてコードを整形 例 例えば CPU 数の変更。GUI では『仮想ソケット数』と『ソケットあたりのコア数』を別々に指定できるけど、PowerCLI では以下の通り、“コア数の合計” しか変更できない（正確にはここで変わるのは『仮想ソケット数』で、『ソケットあたりのコア数』が 1 で固定される）。\nSet-VM \u0026lt;vmname\u0026gt; -NumCpu 4 # 仮想ソケット数 4、ソケットあたりのコア数 1 に変更される ここで Onyx を使って、試しに仮想ソケット数を 2、ソケットあたりのコア数も 2 にしてコードを吐かせてみる。実際の出力が以下。\n# ------- ReconfigVM_Task ------- $spec = New-Object VMware.Vim.VirtualMachineConfigSpec $spec.changeVersion = \u0026#34;2012-11-xxTxx:xx:xx.xxxxxxx\u0026#34; $spec.numCPUs = 4 $spec.numCoresPerSocket = 2 $_this = Get-View -Id \u0026#39;VirtualMachine-vm-416\u0026#39; $_this.ReconfigVM_Task($spec) そんなわけで、仮想ソケット数とソケットあたりのコア数をばらばらに指定するには、VirtualMachineConfigSpec 型のオブジェクトを作って、numCPU に合計コア数、numCoresPerSocket にソケットあたりのコア数を入れてあげて、View オブジェクトの ReconfigVM_Task メソッドにそれを渡せばいいらしいことが分かる。\n仮想ソケット数そのものを表すメンバ変数は無さそうで、これは numCPU を numCoresPerSocket で割った値が使われていそうだということも想像がつく。例えば 1 ソケット 4 コアにしたい場合は、numCPU にも numCoresPerSocket にも 4 を入れてあげればよさそうだと判断できる。よくわからなくてもてきとうに数字を変えて実際にやってみればよいわけで。\nで、このままでも使えなくはないけど、要らないところやまわりくどいところを少し直してあげる。\n4 行目、これは変更時刻情報なので丸ごと不要。8 行目、人間がコードを書く場合は ID 指定ではなくて名前指定のほうが楽なので書き方を変えてあげる。最終的には、例えばこんな感じ。\n# ------- ReconfigVM_Task ------- $spec = New-Object VMware.Vim.VirtualMachineConfigSpec $spec.numCPUs = 4 $spec.numCoresPerSocket = 2 $_this = Get-VM \u0026lt;vmname\u0026gt; | Get-View $_this.ReconfigVM_Task($spec) Get-View はもっといろいろやり方がある（Get-VM しないで Get-View に -Filter で @{“Name” = “”} 渡すとか、別の変数に VM 型のオブジェクトがあるなら Get-View にそれのメンバ変数 Id を渡すとか）し、もっと行数を減らせなくはないけど、このくらいがいちばん分かりやすい気がしている。\n注意 Onyx 経由の vSphere Client ではコンソールは開けない。ポートが 902 と 903 の通信はだめっぽい 削除系の操作はトレースできない？ VM 消したりとか。細かく挙動追っていないので詳細不明 懸念 ここまで書いておいてアレなんだけど、vSphere 5.1 からは vSphere Client ではなくて vSphere Web Client が標準になってくるので、Onyx たんがアップデートしてくれないといずれ使えなくなる疑惑がある。5.1 ではまだ vSphere Client は提供されているけど、今後どうなるか不明だし、そもそも現行の Onyx たんが 5.1 で動くのかまだ試していないのでした。内部的な通信のアーキテクチャが変わっていたとしたらアウト。\nまとめ Web Client が標準になったからいつまで使えるかわからないけれども、GUI でできるのに PowerCLI でどう書けばよいかわからない操作はとりあえず Onyx にコード吐かせて眺めてみるとよいと思います。\n","date":"2012-11-04T15:53:16Z","permalink":"/archives/505/","title":"vSphere Client の操作を PowerCLI のコードにしてくれる VMware の Onyx がすごく便利"},{"content":"学年が上だからといって、下の代に対して “万能” である必要はない。\n万能であろうとすることすら必要ないし、万能であるかのように振る舞うことも、万能でないことを隠そうと取り繕うことも、やはり不要である。\n舞台に乗ること、乗ってそこで演奏すること。同じ舞台で同じ時間で同じ曲を弾くわけで、だから目的は全員同じ。\nそれに向けた練習をする中で、“教える” ことが上意下達から抜け出せないのはなぜなのか。\n“意識する” に代表されるいわゆる “便利な言葉” は、よくよく考えればこの世界には存外に多い。本質をつかむことなく鵜呑みされたこの手の何やかやは、ぼんやりとした理解とともにひたすらに蓄積されるだけ。だから何かコメントを求められて、取り急ぎ自分の中の “言葉集” からふたつみっつ取り出して声に出してみたところで、それに建設的な要素があるかといえばもちろん否であるわけで。\nだから結局、その言葉がほんとうの意味で示す内容──教えようとしていること──の高低感よりも、実は “言葉集” の大きさ自体が、“上” と “下” を無自覚に隔てる要素でもあるのかなあと、外から眺めていて、あるいはウン年前の感覚をひねり出して、少し思う。\n言葉で語るよりは、音で語る、うたで語る、感覚を伝える方が、素直に面白い。上も下も右も左もない。\nああいう音が出したいというイメージ、ああいう音楽が作りたいというイメージ、自分の中の究極の形、“理想” があるかないかの違いは、たぶんすごく大きい。妄想でも夢でもいいから、知人友人先輩後輩上司部下家族仲間その他諸々、あのひとみたいな音が出したいなああのひとみたいに生きたいなあという、ある種とても原始的な欲を持つこと。欲を持つことに素直になること。理想の自分をつくること、それに自分を同化させること、あるいはそうなろうと試行すること。\n誰にも言う必要はないから、こっそりひそかにそうやって上を見ていられたら、たぶんいつもの楽器もちがう音を出してくれるような、そんな気がしているのでした。\n","date":"2012-10-18T15:15:55Z","permalink":"/archives/499/","title":"高校生と部活と、万能な何か"},{"content":"『司会を務めさせていただきます』 『発表させていただきます』 『書かせていただきます』 『出版させていただきます』 『販売させていただきます』 『消灯させていただきます』 『発車させていただきます』 『開催させていただきます』\nとか、そんなに謙遜してどうするのっていうくらい、オトナのしゃべる語尾に、『させていただきます』が当たり前のようにつくようになって久しい。謙遜というか卑下というか、とにかく自分をおとしめすぎててきもちわるい。これは文法的な話じゃなくて感覚的な話ね。もっとドシッと構えてスマートに振る舞えないのかなってその言葉に触れるたびに思う。\nもちろんそれが正しいときもあるよ、『本当はダメだったことを許可を得て』させてもらうときとかね。逆にいえば、そういうニュアンスではないときは使わないほうがスマートだと思うの。\nそれなのに、たとえば電車のアナウンスで『ドアを閉めさせていただきます』とか、『発車させていただきます』とか。\nこっちはただのサービスの享受者なんですよ、サービスの提供者がそうするといったらそれに従うんですよ、どうぞお好きなタイミングで閉めてください、発車してくださいよ、そんなへりくだらないでくださいよ、って思う。\nあっちの提供するサービスに対して、それ相応の対価を払ってそれを享受するのがビジネスってもんでしょ。だからこっちは支払った対価以上の質を求めちゃいけないでしょ。ドアを閉めるタイミングとか発車するタイミングとか、そっちが決めることでしょ。こっちはそれに従うのが当たり前でしょ。それをなぜ『お客様が閉めてはいけないとおっしゃるところ私どもの勝手でたいへん申し訳ありませんが閉めさせていただきます』みたいなニュアンスでアナウンスするの。駅員さんはもっとエラくていいはずでしょ。\nたとえばブログのエントリの冒頭に『～について書かせていただきます』とか。こっちは読み手なんですよ、書き手は書きたいことを勝手に書いてくださいよ、読む読まないはこっちが勝手にしますから、って思う。読みたいものを読むだけなんだから、書きたいことを書けば良いでしょ。それをなぜ『読者の皆さまがこのような内容は書いてはならぬとお考えなのは承知しておりますがこの件だけは書かせていただきたい』みたいなニュアンスで書きだすの。おかしい。書き手さんはもっとエラくていいはずでしょ。\n基本的に『させていただく』っていうのは、冒頭でも書いたけど、\n本当はダメなことを 特別に許可してもらった 状態で使うべきもので、そうじゃないシーンで無意味に使っても、私はそれをとてもしらじらしく思う。本心でいつも上に書いたような状況だと思っているのならそれはそれで状況の把握ができてなくておかしいし、とりあえずへりくだっておけばいいやみたいな意識の伴わない口癖として出てるだけならそんな余計なポーズはきもちわるくておかしい。必要以上の謙遜とか卑下は滅んでいい。\nサービスの提供者がいて、享受者がいる。その間って必ず何らかの等価交換（金銭なり情報なり行為なり）があって、だからそうである以上両者の関係って、少なくとも『対等』のはず。何を間違っても、『享受者のほうがエラい』なんていう関係にはなりえない。\nこのあたり、どうも『享受者のほうがエラい』思考が、享受者側に蔓延してるような気もするんだよね最近。『お客様は神様だ』って、お客様側が言ってどうするのよっていう話。で、サービスの提供者は提供者で、享受者側のそういう風潮を汲んで、とりあえずへりくだっておけば文句ないだろみたいな空気だすの。なんだかなー。\nもうちょっとね、サービスの提供者側はね、もっとエラそうにどしっと構えていいと思いますよ。そして自分が提供者側になったら、『～させていただきます』じゃなくて、『～いたします』ってしゃべりたいですね。\nとはいえ、『させていただきます』って言っておかないと怒るエラいひともいるんだろうなーと思う。ポーズだけでも取っておかないとうまくいかないことがあるのが、オトナの世界のいやらしい話。めんどくさいですね。\n以上、以前書いたはてな匿名ダイアリ からそのまま転載。リンク先本文いちばん最後の『。』がこっそり本人証明。\n","date":"2011-10-08T06:05:11Z","permalink":"/archives/491/","title":"『させていただきます』っていう言葉がきもちわるい"},{"content":"自分で自分の発言引用するのってちょっとかっこわるいね。ついったからです。\n今日はぼくの出身高校の文化祭で、ぼくが所属していた部活のOB会みたいなプチ集まりがおひるすぎにあって、そこで文化祭とは別に年に一度ある『定期演奏会』っていうのでいわゆる『OB合奏』みたいなのをやりたいなあ、みたいな議論がちょっとあって、そこでわやわやなった、みたいな流れ。うん、あんまり説明する気ないです。\nせめて部活はOBのものじゃないんですよ、ということだけでも言っておけばよかったかな。通じるとも思えないけれど。理不尽なことは往々にしてあるものですね\n\u0026mdash; くろい (@kurokobo) September 11, 2011 OBってなんだろうなーってずっと考えてるけどよくわからなくてもやもやしてる。演奏会って現役のためのものなのになんでOBが好き勝手コントロールしようとするの(・ω・) ただの暴慢でしょそんなの(・ω・) 勝手に話進めてあとは従わせようとかそういう思考にはまるで共感できない\n\u0026mdash; くろい (@kurokobo) September 11, 2011 OBがOBっていう地位にあぐらかいてさあお前らあがめたてまつれ奉仕しろ、みたいな姿勢は本当に気に入らない。自分は何もしない徹底的な受け身なのに『現役が○○してくれない、△△をよこさない』とか、現役はOB中心で考えてくれて当たり前、みたいなの、本当に嫌だ\n\u0026mdash; くろい (@kurokobo) September 11, 2011 現役が90人近くいて、資金難なわけでもないのに、わざわざ現役の手を煩わせてまでOB合奏をやろうとして、それで現役にデメリットを上回るだけのメリットを与えられるわけ？ OBの欲に現役を付き合わせるだけの意味があるわけ？ 『やりたいからやる』はまず現役のそれを優先させるべきでしょ\n\u0026mdash; くろい (@kurokobo) September 11, 2011 『現役から連絡来ないから、ああOB要らないんだなって思った』って、そりゃまさにその通りに必要とされてないから連絡こないだけなんだろ☆ とか思っちゃうよね。実際OB入れてやるより現役だけでやったほうがいいっていう判断があったわけだし、なんていうか、もう文句言うな☆\n\u0026mdash; くろい (@kurokobo) September 11, 2011 書いたらいくつかリプライをもらいました。そうだよねえ！ ってかんじの。\n@kurokobo よくわからんけど、OBて現役のためにボランティアする集まりの名前じゃないの？俺の知ってるOBてそうなんだけど(´・д・｀)\n\u0026mdash; せんよう (@PlemiamMABO) September 11, 2011 @kurokobo OBってアレだろ、奢るだけの財布だろ\n\u0026mdash; kiwamin (@kiwamin) September 11, 2011 OBっていう生き物は不思議です。いろいろなひとがいますよね。いいことをいうOBもいれば、いいこととはちゃめちゃなことをどっちもいうOBもいます。全部が全部はちゃめちゃなひとってあんまりいないので頭ごなしに全否定はできないんだけど、でもやっぱり、ぼくはぼくの考える『OB』っていうのの貫くべきところは貫いていきたいですしね。もやもやしますね。\nひとまず、部活とその演奏会はOBのものではなく現役のものである、というのがぼくの信念なので、そこは曲げたくないところです。\n","date":"2011-09-11T13:52:40Z","permalink":"/archives/488/","title":"OBってなんだろうなあとかそういう話"},{"content":"自宅サーバ（Ubuntu 8.04）にWebプロキシを立てた。入れたのはこれ。\nCGIProxy 2.1beta19 そもそもうぶんつさんを最近触っていなかったのでapacheがどういう状態かもはやよくわからなくなってたんだけど、とりあえずOpenSSLは入れてたっぽいので、オレオレ証明書を発行してhttpsアクセスを有効化させるところからはじめ。\nそこに上のCGIほうりこんだらさくっと動いたんだけど、httpsなページにhttpなproxy経由でアクセスしたら警告でた（当たり前……）のでNET::SSLeayとやらをapt-getしてどうのこうのしたらどうにかなった。簡単でした。\n無事に動いたはいいけど、しかし重いですね(´ω｀)\nあとは動画系がうまく見られないので要確認。Flashコンテンツまわりのオプションいじったらとりあえずプレイヤは表示されたけど再生はされないかんじ。サーバのスペックもあやしい(´ω｀) そもそもプロキシ経由で動画みるのがまちがってる気もする。\nこの辺の手順はまとめておきたい。あとOSはCentOSにしたい。\n","date":"2011-08-23T12:42:51Z","permalink":"/archives/481/","title":"Webプロキシ"},{"content":"そつろんをLaTeXで書くことになったんだけど、研究室にテンプレートが無かったのでせんぱいのを改造したりインターネットで調べたりしつつ作った。細かいところはいろいろセオリィに反しているのかもしれない（よくしらない）けど、簡単につかえるようにはしたつもり。\nうちの研究室用なので他の団体様で使えるかはわかりませんが、使えそうならどうぞ。\nhttp://url.kurokobo.com/latex 卒業論文用に作ったけど、ちょろっと書き換えれば修士論文にもたぶん使えると思う。文字コードは研究室で使う関係でEUC-JPです。\n中の[main.tex]をいじくり回せばどうにかなる。そのままコンパイルすると説明書みたいなのが出力される（同梱の[main.pdf]がそれ）。簡単なLaTeXコマンドの解説も書いたので、たぶんだいたいどうにかなる。わからないところはぐぐればどうにかなる。\n@ymrl も卒業論文をLaTeXで書く解説してくれていますし、今のわいらぼ三年生、来年がんばってくださいヽ( ・∀・)ノ\n","date":"2011-01-18T15:09:47Z","permalink":"/archives/146/","title":"卒業論文のLaTeXテンプレート"},{"content":"いろいろ方法まとめ。\nまずはインストール方法。Mac OS XとWindowsでGCLとSBCLをいんすこする。MacにGCL入れるのが一番面倒なんだけど、そのかわり個人的に一番使いやすいのもMac上でのGCL……。\nLisp/GCLの利用（Mac OS X） - kuro-tech\nhttp://url.kurokobo.com/lispgclmac Lisp/SBCLの利用（Mac OS X） - kuro-tech\nhttp://url.kurokobo.com/lispsbclmac Lisp/GCLの利用（Windows） - kuro-tech\nhttp://url.kurokobo.com/lispgclwin Lisp/SBCLの利用（Windows） - kuro-tech\nhttp://url.kurokobo.com/lispsbclwin 次がバグなんだか仕様なんだかわからないけど、SBCLでprintとreadを続けて書いたときに、なぜかprintより先にreadが呼ばれてしまうというよくわからない挙動に対応するための方法。print -\u0026gt; readの流れをprin1 -\u0026gt; terpri -\u0026gt; readにするだけ。\nLisp/SBCLでprintとreadの実行順序が逆になる場合の対処 - kuro-tech http://url.kurokobo.com/sbclprintread ","date":"2010-10-20T07:10:13Z","permalink":"/archives/141/","title":"GCLとSBCLのインストール周りとSBCLのバグっぽいのの対処"},{"content":"大学の授業でLispをいじることがあった。大学の共用PCはほとんどMacで、そこにはgclとkclが入っていたので、自分のMacBookにもgclをインストールしたいなァということで挑戦。すんなりいくかと思いきやいろいろバギィで難儀したけど、ぐぐりつつどうにかできたので別ページにまとめ。\nLisp/GCLの利用（Mac OS X） グーグル先生は偉大ですね！\n学校の環境だと上下キーでの履歴参照とか左右キーでのカーソル移動とかなにもできなくて、ものすごくストレスのたまるコーディングを強いられていたんだけど、自前でビルドしたら上下キーも左右キーも使えてさくさくでうれしいです。\n学校の環境と自分の環境で何が違うのかよくわかっていないので、学校のもいじればどうにかなったりするんですかね。\n","date":"2010-10-11T18:37:51Z","permalink":"/archives/135/","title":"Mac OS XでGNU Common Lisp (GCL)をつかう"},{"content":"ぼくにしかできないことが、何かあってほしいと思う。\nいろいろなコミュニティに所属していて、どのコミュニティにもいろいろな人がいて、そうしたときに、そのコミュニティの中でのぼくの役割って何だろう、ということを、たまにふと考える。\nぼくがそのコミュニティに居ていい理由はなにか、居ることが許されている理由はなにか。ぼくが『空気』でないのなら、ぼくにはぼくの役割があってしかるべきで、すなわちそれがぼくの存在意義というか、そんなようなものだったりするわけで。\n誰かに必要とされれば嬉しいし、誰にも必要とされなければ虚しい。これはわりと誰にでも共通する感覚だと思うのだけれど、はっきりと『なぜ自分が必要とされているか』を自覚できているひとはそこまで多くないのではないかしら。\nこの話は黒井に聴かせよう、このことは黒井に頼もうといわれる、これを委ねるのは必ず他でもないあの黒井でなければいけないと、そういわれる、思われる『なにか』がほしい。\n他人がぼくに何かを求めてきたとき、狙いはぼくの『能力』なのか、『人間』なのか。そこもまた気になるところであり。\n人間力というものはこういうことをいうものなのかなあと、漠然とした実感。社会に出たあと生きるのは、たぶんこういうところ。能力を搾取されるのではなくて、人間を求められるひとになりたいですね。\n","date":"2010-10-10T18:28:00Z","permalink":"/archives/131/","title":"役割"},{"content":"初めてギターに触って、もう8年目になった。まだ8年目、とも思うけれど。\nギターアンサンブル、という世界がある。大小さまざまな大きさのクラシックギターをつかって、数十人で合奏する。小さいギターは高い音が出るし、大きいギターは低い音が出る。言うなれば、バイオリンやらチェロやらをつかういわゆる『弦楽合奏』のギター版、とでも言うべきものだ。\nさて。\n音楽というものが嫌いだったぼくが、ぼくだけの音楽を求め始めたきっかけは、友人——と呼べるほど当時はまだ仲良くなっていなかったかもしれないけれど——の、ほんの些細な、しかし運命的な、軽い一言だった。\n——仮入部に行ってみよう、ギターアンサンブル部の。\n高校入学直後、とりあえずでもいいから自分の『居場所』を作ろうと、席が近い人々と当たり障りなく行動を共にしておく、ありがちなあの空気の中での一幕。部活は何にしようか、それまで野球部だったけれども高校の野球部ほど本気で打ち込む気はないし、帰宅部はつまらないし、と思案していた中での一幕。\n音楽か、このぼくがか、正気か、と、まずは思った。まるで予想しなかった、ぼくにとっては斜め上を行く展開。\nぼくは音楽が嫌いなんだと、思っていたのだけれど、結局、ぼくは彼に従って仮入部に足を向けていた。魔が差した、とでも言うか。ギターなんぞにこれっぽっちも興味はないけれど、少なくとも暇つぶしにはなるし、ほかに行くあてもないから良いかと、まったく積極性を持たない動機。ぼくはとても、さめていた。\nものごとを嫌いになるきっかけも、ものごとを好きになるきっかけも、あとから考えればどうでも良いことだったというのは、よくあることだ。ぼくの場合、音楽を嫌いになりはじめた理由なんて、小学校と中学校の音楽の先生が嫌いだったからという、ただそれだけのこと。音楽の先生に対する嫌悪感が、音楽そのものに対する嫌悪感であると思い込んでいた、あるいは錯覚していた、たぶんその程度のものだったと、今では考えている。\nその程度の『嫌い』は、だから本当に簡単にひっくり返る。ほいほいと仮入部に連れて行かれ、ギターという何やら木でできたハコを持たされ、黒い丸と棒の並んだ紙——楽譜という——と数十分にらめっこさせられ、さあみんなで合わせましょうとその場にいた数十人で一斉に音を出させられただけの、お遊戯とも区別のつかないレベルの『遊び』。\nかと思いきや、その『遊び』をした瞬間、たぶんぼくはもう入部を決めていた。高校生にもなって弾いたのが『ちょうちょ』であるとか、楽譜の端のイラストがヘタクソだとか、そんなことはどうでもよかった。ああそうか、こういう世界があるのかと、それはなかなかに衝撃的で、とても魅力的で、『音楽』と呼ぶにはほど遠いひどい音しか出なかったけれど、音楽は楽しいと、理解して、実感して、満足した。\nそんな出来事から、かれこれ7年と半年が経過した。いまぼくは、そのギターアンサンブル部での経験を経て、社会人中心の合奏団に所属して、高校時代の恩師の元でまだギターを続けている。部活の練習に顔を出したり、楽譜を作ったり、演奏会やコンクールで簡単な手伝いをしたりしている。満足かと聴かれれば、満足だと胸を張って言える。\nと、ここで終わればめでたしめでたしなのだけれど、最近、当時のことを思い出すことが多くなった。あの時に戻ってみたいと思ってしまう。今高校でギターを弾いている彼らが、ぼくの戻れない世界と時間を謳歌している彼らが、白状すれば、羨ましい。\nなぜ高校に教えに行くのか、という質問に、あまり答えたくない。動機に『逃避』が含まれることを、認めたくないからだ。\n自覚はしている。思い出にひたって甘えてばかりいるわけにはいかないし、そもそもそんなことが許されるヌルい環境にいるわけでもない。どうしようもないなあと自覚しながらも、それでも楽な方、甘い方に進みたくなってしまうのは、どうにかしないといけないのだろう。\nそんなことを思うのは、最近、ぼくにとって『甘いモノ』であるはずだったギターの世界が、ひょっとしてこれは負担になり始めたのではないかという、漠然な危機感を覚えたからだ。これが事実なら、言い換えれば、縋るべきところがなくなりつつある、ということ。逃げ場がない。\nいい加減、次の手をどう打つか、考えないといけない。不器用なのだから、人に何かを言う前に、自分の身の振り方を考えないと。\n","date":"2010-10-08T17:06:09Z","permalink":"/archives/120/","title":"ギターとぼく"},{"content":"いかすみを初めて食べた。存外においしかった。\nぼくはだいたいいつでも、服が黒い。服が黒いから黒井と呼ばれるようになったわけだし、まあそれはいいんだけど、とにかく、そう、服が黒い。\nだから想像通りというかなんというか、ぼくに『好きな食べ物って何？』と聞いてきた人は、こっちが何かを言う前に『いかすみ？』と追撃してくる。のだけど、今まで食べたことがなかったので、機会があったら食べたいなあと思いながら過ごしてきたわけです。\nただぼくは、幼少期からずっとそうなんだけど、見た目の悪いものは食べたいと思えない人だったのです。そんなわけで、小さい頃は貝類（ぬらぬらでちょうグロいもんねとくにサザエ）とか、ぐちゃぐちゃになにかが混ぜ込まれた何かとか、あんまり食べられなかった。\nぼくにとってはいかすみもその『見た目の悪い』もののひとつで、だって黒いんですよ、食べ物なのになんで黒いの。黒いのにおいしい食べ物って他にありますか。ないでしょう。海苔はまああれはおいしいけど、あれは味付けの核にはなりえない風味づけ目的のものだし。\nという感じで、食べたいけど食べたくない境目を行ったり来たりしていたのだけれども、おいしいイタリアンのお店に入ったときにいかすみのスパゲッティがあったので思い切って注文してみたわけです。\nそのお店は二回目で、何を頼んでもおいしかったから、いかすみにしても必ずちゃんとしたほんもののいかすみを出してくれるだろうという確信があったわけで。思い切りました。\nそしたらおいしかったです。おちはありません。でもあれだね、口が黒くなるね。\n","date":"2010-04-21T09:08:42Z","permalink":"/archives/99/","title":"いかすみ"},{"content":"今年の就職活動は、氷河期の再来だの超氷河期だの言われている。世間一般では。\nでも正直、就職氷河期とか、あと不況とか、そういうネガティブな風潮って、それが蔓延するいちばんの原因は『みんながホントにそういう気になっちゃうこと』だと思っている。\nその人にとって『初めての』就職活動のはずなのに、『去年より』厳しいと断言できる感覚が、ぼくにはよくわからない。人間が変われば難易度が変わるのは当たり前のこと。去年就職活動をしていた人と、今年就職活動をしている人は、別の人なんだから、比べて何かがわかるものでもないのではないかと。\n『ポジティブに生きよう☆』と言うのなら、まず蔓延した『今年の就職活動は厳しい』というよくわからない『空気』を、そんなものは幻想だとポジティブに捉えたい。\n去年は去年、今年は今年。厳しいか厳しくないか、世間の評価など、そんなものはどうでもよくて、厳しいと思い込むことをまずやめた方が、視野は広く保てると思う。\n","date":"2010-03-28T17:53:32Z","permalink":"/archives/94/","title":"氷河期なう？"},{"content":"就職活動というものは始めた当初は厄介なだけかと思っていたけれど、はじめると意外とそうでもないと思える。\n自分とまったく違う立場の人と話せるとか、企業がこんなに情報を出してくれるのは今だけだとか、役員が自分の話を聞くためだけに時間を割いてくれるとか、まあそういうまじめなことは今は置いておくとして。\nらーめんが食べられるのだ。行く先々で、おいしいのが。\n最近は便利なもので、おいしいお店の情報がインタネットですぐ手に入る。口コミに依拠した評価だから、どこまで信用できるか怪しいものもあるけど、でもそのランクで上位のお店に、ハズレはなかなか少ない。\nちょーおいしいらーめんじゃなくていい、おいしいなあって思えるくらいのらーめんでいい。あれやこれやと批評せずに、ほどほどのおいしさで満足できる人間だから、口コミサイトの評価を基準にしてもまったく問題なくおいしいのだ。\nさすがに面接前にらーめんを食べるというのは（口臭的な意味で）リスキィだからやらないけど、面接がお昼くらいだったら、朝ごはんを遅めにして面接後に遅めのお昼ごはんとしてらーめん、とかはよくやる。面接は楽しいしらーめんはおいしいし、素晴らしい一日の出来上がりです。\nしゅうかつがイヤなら、しゅうかつにくっついてくる、あるいはくっつけられる楽しみを見つけるのも、一つのソリューションです。\n","date":"2010-03-27T16:42:55Z","permalink":"/archives/90/","title":"就職活動の利点"},{"content":"ギターを嗜むときの感覚の話。\nギターの調子がよくて、爪の調子がよいとき、身体の調子もよくなって、結果としてぼくは良い音をつかまえられる。\n空間には、すでに音が、聴こえない音が存在している、と考える。ギターという楽器を通じて、ぼくらはその聴こえない音をつかまえて、表出させる。そんな感覚がある。\nただの物理現象でも、気の持ちようで音が変わるのはなぜだろう。厳密に言えばたぶん錯覚で、同じ『音』をどう解釈するか、その時の立脚点が心理状態によって変わるっていう、それだけの話だと思うけど。\nでもやっぱりそれでも、錯覚でも、ぼくにとって『変わる』のは確かなわけで。だから気の持ちようで音が変わるのは、事実なわけで。\n感覚で語るなら、錯覚も歓迎されるべきだと思う。お客さんに錯覚を起こせたら、それをたぶん、大成功って呼ぶんだろう。\n","date":"2010-03-27T15:59:51Z","permalink":"/archives/86/","title":"空間と音"},{"content":"ぼくが、IT業界と、テレビ業界の技術部門のどちらにも興味を持っていたころの話。\n某テレビ局の人事が、待機部屋でぼくとの雑談の中でふと言ったことがあった。\n曰く、テレビは感覚の世界で、ITは理論の世界であると。テレビ業界はたかだか数十秒、数秒の映像に、何十日、何十人もコストを割く、きわめて非効率な世界であると。それに対してIT業界は、いかに時間を短くするか、いかに人を減らすかに注力する、まさに効率化を追求する世界であると。\nそして言うには、『どちらも行きたい、という学生がたまにいるけど、でもぼくが考えるにね、この二つの世界って、根本的なところがまったく逆なんだ。だからたぶん、どちらにも適した人間って、ほとんど居ないと思う』と。自分のベクトルがどちらに向いているのか、そこを見極めないと、自分に合わない方向に進んでしまったら悲惨だよねと、にこやかにその人事は語った。\nこの説明、ぼくはなかなかけっこう気に入っている。もちろんそれぞれの具体的な仕事内容を細かく比べれば、お互いがお互いの性質を内包するところは必ずあるだろうけれど、でもそれは知らないと、体験しないとわからないから、就職活動中のぼくらから見える範囲には、おそらくない。\n将来の自分を想像するときはあるけれど、でも結局それは、今考えられる範囲の中での想像でしかない。社会に出ていないぼくらが考えられる『世界』と、実際に社会の中で動いている社会人が考えられる『世界』と、絶対的な差があるのは確実だとおもう。いろいろと学んで経験した五年後に考える『ぼくの将来像』は、今考えられる限界の外にある。今考える『ぼくの将来像』は、『今のぼくが考えるぼくの将来像』であって、『五年後のぼくが考えるぼくの将来像』とはまったく違うものであるはずだとおもうのだ。\nだから、どの業界が自分に向いているかを考えるときに、『リアル』な想像は不可能だ。それぞれの『なんとなく』特徴的なところだけを見ることしかできなくて、その『なんとなく』な世界のなかで、どうにかこうにかおぼろげに、『進むべき方向』を探るだけ。でもだからこそ、そうしたときに、例えば先のITとテレビのざっくりとした抽象的な二極構造は、逆にひどくスマートだと、ぼくにはおもえたのだ。\n","date":"2010-03-24T16:20:32Z","permalink":"/archives/79/","title":"IT屋と、テレビの技術屋"},{"content":"ギター関係の先輩に、現職のSEがいる。\nこの前、練習後に飲み会があって、そこにその先輩が来たので、いろいろと話をした。大学生？ そうです三年生ですよ、という話になれば、しゅうかつ？ というところに話が飛ぶのはもはや当然の流れでもあり。\n志望業界がどこなの、というところから、いわゆるSIerで、なりたいのがITコンサルタントとか、プロジェクトマネージャとか、そこいらですね、というところをこちらから話したのだけど、その先輩はあまり良い顔をしなかった。曰く、コンサルタントやプロジェクトマネージャって好きじゃないんだ、と。\nその先輩は現職のプログラマとしてキャリアを積んできているひとで、コンサルタントやプロジェクトマネージャにお仕事を頼まれる側の立場。そういう立場でずっとやってきた目で見れば、端的に言うと結局『上は下を分かっていない』というところに行きつくようだった。\n先輩が言うには、何を作るにしても、上が考えたシステムを実際に具現化する際に必要なのは『技術』であって、技術を扱うプログラマである、と。プログラマ、すなわち自分たちがいなければ、いくら上ががんばろうと絶対にモノにはならない。プロジェクトに最終的に一番必要とされる、一番大事な核はプログラマにある、と。\nそれなのになぜか、どうも見下されている空気がある、と。技術軽視な空気。\nどこの会社でもそうだとはいえないにしても、少なくともこういう傾向のある会社はあるんだろう。これはわりとシビアな問題で、キャリアパスが『プログラマ』に終始するような進路は、実際会社説明会に行ってもあまりプッシュされない、というかほとんど存在が見えない。もちろん観測範囲の問題でもあるだろうけれども。\nで、一応、大手のSIerは『下流も知らないと上流はできない』という思想のもとで、研修段階や入社後数年はがっつり下流を担当させるところが多いから、ぼくはその先輩に、最近はそういう流れがあって、技術軽視もそこまで強くないのではないか、という話をした。\nそこで言い返されたのは、『その思想の場合、下流の体験は上流に行くための、ただのステップでしかない。結局下流が重要視されているわけではないだろう』ということ。技術部分の体験は、あくまで成長の一段階でしかなくて、そこで止まるというパスは誰も見ていないし、会社側も考えていないのではないか、と。結局、そこそこ軽視されている気がする、と。\n否定できるところが見つからなかった。わりと揺さぶられた気分。別にその先輩は、ぼくにコンサルタントやらプロジェクトマネージャやらそんなものを目指すなと言いたいわけではない。あくまで自分の考え、こういう立場もあるのだと言いたかったんだと思う。\nで、後日。\n某大手SIerの最終面接で、ぼくはこの話を振った。先輩にこういう考えの人がいて、ぼくはうまい答えが見つけられなかったのだけれど、現職SEのこの感覚を、会社としてどう考えるか、といったようなこと。\n面接官は現職のプロジェクトマネージャ。私の考えだけど、と前置きして話してくれたのは、『プログラマの立場は会社全体として確実に知らなければならない』ことと、『プログラマがどう感じるかどうかもマネジメント次第でもある』こと。プロジェクトにかかわる人たちが、自分の扱われ方をどう感じるか、楽しいと思うか見下されてると思うか、そこもプロジェクトマネージャの手腕の問われるところだから、ぜひそこまで考えられるプロジェクトマネージャを目指してください、ということを言われた。\n知らない限り分からないことって世の中ものすごく多くて、でもそれは知らない限り分からないからどうしようもない。ぼくはその先輩の立場と考えを知って、面接官だったプロジェクトマネージャの立ち場と考えも知って、そこから分かったこともあった。あったけど、体験ではないから、まだ、今見える範囲の中で目指すべきところを探すしかなくて、だからぼくは今の時点では、今までどおりのキャリアパスを目指そうと考えた。\n先輩の話とか、OBの話とか、社員の話とか。\n就職活動の中で、ものすごく重要視されてしまいがちな傾向がある気がするんだけど、失礼な言い方になるのを承知であえて言えば、しょせんその人たちもone of themでしかないと考えている。情報の重みづけをする権利は、自分自身にある。取捨選択も自分ですればいい。ただし、どうなっても責任は自分で取るしかない。\n受けた話の活用は、自分自身で考えたい。だからぼくは、ふたりの社員の話を、ぼくなりの考えで糧にして、吸収することにした。がんばろうと思った。\n","date":"2010-03-21T09:23:45Z","permalink":"/archives/114/","title":"現役SEに言われたこと"},{"content":"まだ起きてる。\n時間がズレてきてるなあ。よろしくない。\n日経の夕刊にしゅうかつのことが載っていた。がんばらないといけないようだ。\nがんばる、というのは意外と難しい。今まで明確になにかをがんばった記憶があまりなくて、ゆるゆるとしているうちに、いろいろなことがうまく進んでしまっているという感覚は否定できない。とよく思う。\n高校の部活はがんばってた。でももっとがんばれた気はする。本気を出していた、という気はするけど、本気は本当に本気だったのかしら。\nがんばった気がしない、ということは、二つの捉え方ができる。\nひとつは、本当にがんばっていない、という、絶対的な評価。のらりくらり。のんべんだらり。だるだるだるーん。\nもうひとつは、がんばっていたけれどあとから思えばそれは『がんばった』に値しなかったという、相対的な評価。分かりやすくいえば、がんばったことを忘れている、というパタン。行為の瞬間は確かにがんばっていたけど、冷静に振り返ると、あれ、別にがんばってなくね、みたいな。\nぼくの場合、後者が多い気がしている。そう思っておくことにしている。前者はあまりにもかなしい。\n過去のがんばっていた自分を、今、がんばっていなかったと評価できるということは、それだけ自分が『がんばった』と評価できる閾値が上がった、ともいえるんだろう。成長した、キャパシティが増えた、ということだろう。たぶん。\nがんばる、というのは、けっこう思い込みも大きい。\nがんばる、と、つかれる、は、ちょっと似ている。身体を動かして、あるいは頭を動かして、なにかしたとき、運動でも知識でも、とにかくなにかアウトプットしたら、それ相応のエネルギィは消費されて身体から抜けていく。\nその抜けていったモノが、『がんばり』と評価されるか、『つかれ』と評価されるか、そこはたぶん、どう思い込むかが違うだけ。現象としては、あまり差はない。\nだから、満足するかしないか、っていう軸が、何をするにも大事な気がする。満足したら、がんばったと思えばいいし、満足しなかったら、つかれたと思えばいい。\nというようなことをもやもや考えているから、だからぼくはあまり『つかれた』というアウトプットが好きではない。しちゃうけども。\nポジとネガ、まあ、そういうことかしらね。\n就職活動のなかでは、がんばったこと、努力したこと、苦労したこと、をだいたい聞かれる。\nどの経験を選んでも、だいたいぼくの口はぼくの考えている以上にうまくそれっぽいことをしゃべってくれるけど、しゃべりながら、ちょっとむなしく思うのも事実なのです。\n結局、その経験を『努力』で評価するか『苦労』で評価するかは自分の判断だし、そしてその判断は、いかに自分がそう思うかっていうところだけが基準。この程度のことをぼくは『苦労』で語るのか。この程度の苦労しかしていないのか。へぼやろうめ。などと。思わないこともないわけだ。\nいやはやなんとも。うーん。\n","date":"2010-03-13T19:13:04Z","permalink":"/archives/108/","title":"がんばること"},{"content":"部屋をあさっていたら高校時代の定期演奏会のVHSが出てきたので、リマスタリングしようと画策。\nとはいってもキャプチャボードを持っていないので、VHSをBDレコーダのHDDからBD経由でPCに入れてDVDにオーサリング、っていう回りくどい方法を取る。予定。\nとはいえ安いキャプチャボードよりもBDレコーダを信用してるし、BDレコーダ通せばフルHDへのわりときれいなあぷこんが挟まるから、画質調整もトリミングもしやすい。と勝手に思ってる。\n画質をちゃんと求めるなら、再生するVHSデッキをもっとちゃんとしたのにするべきだし、そもそものソース映像をきっと先生が持ってるからそこを貰うべきなんだろうけど、まあそこまでしなくてもいいかなと。\nしゅうかつと並行して進めたいですね。二年分。一年のころのと、二年の頃の。\nということを考えながら飛ばし飛ばしこの映像を、つまりぼくが一年生の頃の定期演奏会の映像を、だらだら見てたんですけど、えらい懐かしいですねえ。みんな元気かなーとかちょっと感傷的になってしまう。\nしかしこれ、ギターを続けてる今だからこそ見える部分がかなり大きいですね。当時は当時で間違いなくがんばってたけどねー。\nでもなんというか、こうやって『当時は当時でがんばってた』って今言い切れるってことがすごく大事なことなのかなとか。そんな感じ。\n後悔って後味悪いですからね。\nおわり。\n","date":"2010-03-01T15:16:20Z","permalink":"/archives/104/","title":"部活の古いVHS"},{"content":"はやく帰れたから高校の部活に行こうと思ったのに、『なんじまでっすかー』って電話したときにはとっくに終わっていたという展開でした。残念すぎる。\n自分が編曲した曲がね、８０人以上の大合奏団にででんと演奏されるってね、なかなか感激なんですよね。今日その音出しだったらしいんですよ。だから行きたかったのに！ どんまい！\n編曲らしい編曲をしたのがたぶん高校時代が最初で、マリオのアレですね。\n学年演奏もやったけどあれは弦楽スコアを移調させただけだから編曲ってほどじゃないし、あーでもパート紹介は数えていいのかな。４９期のドラえもんもやったんだっけ？ 結局アレ使われたんだっけ？\nという感じで、実に、４年？ くらいのブランクを経て、この前のサマーコンサートだか何だかの会場で編曲頼まれたのをきっかけに早くも全体合奏２曲と重奏１曲の計３曲を納めましたが、正直ものすごく勉強になります。どうもありがとうみなさま。ぼくはがんばります。ました。\n編曲のクセっていうのはやっぱりあって、それがつまりウマいヘタに直結するもんなんだなあと、編曲という視点で今まで弾いてきた楽譜を見直すとすごく思うのです。ぼくの場合、まだキャリアがぜんぜんないので、一回一回がすごく貴重なトレーニング。\nだからこそ、批判される、という機会を大事にしたいと最近すごく思います。先生に見せてもちょいちょいとした手直ししかしてくれないので正直もっと細かくつっこんでえええと思います。言いにくいですけどね。\n曲のくおりちーもそうだけど、それ以上に楽譜っていう記号的表現の美しさみたいなのも追求したいところ。フォントを変えたり、文字間隔を微調整したり、余白をミリ単位で合わせたりとか結構やってるんだけど、まず誰にも気が付かれないよね。\nただ、楽譜における表現に関して言えることは、気が付かれないことはいいことであるとも思うのです。つまりそれは違和感を抱かせない、目に優しい、見易い、ということでもあるわけで、だからまあ、見難いって言われないってことは、ある程度どうにかなっているのかなあなどとポジティブにとらえています。\nとはいうものの、そもそも絶対的にこみにけーちょんが足りん。ある程度慣れないと、どうしても現役さんにとってはこっちは「先輩」になっちゃうんだよね。でもそこを越えて慣れてからの意見のほうがいわゆる本音に近いから、ぼくはその段階での評価を大事にしたいんですよね。\n先輩って思われるのけっこうぼく嫌いなんですけど、だからまずはカタチからみたいな感じで後輩におめーら敬語使うんじゃねーぞな感じで今まで生きてきましたけど、さすがに５歳以上離れてくると、いくらこっちがよくてもあっちがよくないんだろうなあと気が付き始めてます。距離の測り方が難しいね。こみゅ力高い人すごいと思います。\n高校行ってきたーとか話すと「通報した」とか「犯罪者」とか煽られる（もちろんネタだけどね）こんな世の中ですけど、何が言いたいかっていうと、定期演奏会行けなくなりそうでちょっとうわあって感じだよってことでした。舞台裏の空気大好きなんだけどなー。一年間お預けかと思うとなかなかさびしいものがあります。\nテスト勉強の合間に書きました。もちろん企業向けのエントリーシートは比較にならないほどちゃんと日本語固めてますよ。だいじょうぶですよ。\n","date":"2010-01-21T16:52:01Z","permalink":"/archives/100/","title":"部活とか編曲とか"}]