Red Hat - Open Innovation Labsのブログ - TECH PLAY

TECH PLAY

Red Hat  - Open Innovation Labs

Red Hat - Open Innovation Labs の技術ブログ

全900件

こんにちは。Red Hatでソリューションアーキテクトをしている杉本 拓です。 今回の記事では、サービスレジストリの重要性について解説し、APIとスキーマを一元的に管理するための Red Hat の Service Registry の概要と基本的な使い方について紹介したいと思います。この Red Hat Service Registry は Red Hat Application Foundations (Red Hat Integration) に含まれており、Red Hat のマネージドサービス Red Hat OpenShift Service Registry としても提供されています(この Red Hat OpenShift Service Registry は、Red Hat OpenShift API Management と Red Hat OpenShift Streams for Apache Kafka の利用者であれば無償で利用することが可能であり、トライアルでの利用も可能です)。 マイクロサービスの連携においてサービスレジストリが重要な理由 企業の中に存在するアプリケーションは様々な形でデータの連携を行います。異なるシステムがデータの連携を行うことができるようにするためには、やり取りされるデータのフォーマットや属性をスキーマとして定義します。しかし時間の経過とともにアプリケーションの変更が必要となれば、アプリケーションで使用されるスキーマにも変更が必要となることは多々あります。連携対象となるシステムが1対1であればスキーマの変更による影響は限定的かもしれませんが、企業の中には様々なシステムが存在し、さらに分散する複数のマイクロサービスでやり取りされるデータを検証し、不整合を防ぐためには、アプリケーションの進化とともにスキーマの進化(スキーマエボリューション)への対応を検討しておくことが重要になってきます。サービスレジストリというのはそのためのソリューションとなるものです。 Apache Kafka をマイクロサービス間の連携やデータ連携のためのハブとして利用するケースが増えていますが、Apache Kafka そのものにはデータ検証を行う機能は無いため、スキーマの進化に対応するためにはサービスレジストリを組み合わせて使用する必要があります。メッセージのプロデューサーは Apache Kafka にメッセージを送信し、コンシューマーのアプリケーションがそのメッセージを取得して処理をしますが、プロデューサー側のアプリケーションで属性の追加やデータ型の変更があった場合、コンシューマーでもその対応ができなければデータを適切に処理することができなくなってしまいます。イベント駆動型アーキテクチャにおいては、各マイクロサービスは疎結合な連携をするとしても、プロデューサーが使用しているスキーマをコンシューマーのアプリケーションがあらかじめ把握しておくことは必要であり、スキーマに変更が生じた場合にはどのような変更が発生したのかを知る必要があります。 サービスレジストリはスキーマ定義の情報を一元的に格納するための場所であり、プロデューサーのマイクロサービス開発者はスキーマをサービスレジストリに登録し、コンシューマーのマイクロサービス開発者はサービスレジストリを使用してスキーマを検索し、プロデューサーから受け取ったデータを処理できるように実装します。コンシューマーの種類が増えてくる場合、このサービスレジストリの役割は更に重要になってきます。さらにサービスレジストリを使用することで、プロデューサーもコンシューマーも事前に定義されたスキーマを元に開発を行うことができるため、コントラクトファーストの開発アプローチを採用しやすくなります。 Red Hat が提供するサービスレジストリ Red Hat が提供するサービスレジストリの機能は OSS の Apicurio Registry をベースとしています( Apicurio のサブプロジェクトとしては、 以前の赤帽エンジニアブログ でも紹介しているように、APIドキュメントやスキーマ(OpenAPI、AsyncAPI、Apache Avro、JSON Schema、Protocol Buffer)を GUI ベースでグラフィカルに設計できる Apicurio Studio / Apicurito もあります)。この Red Hat の Service Registry では、Apache Kafka で使用されるスキーマを格納することができるだけでなく、OpenAPI のドキュメントや Apache Kafka のプロデューサーとコンシューマーが使用する非同期メッセージングのためのAPI仕様である AsyncAPI のドキュメントも一元的に管理することができるようになります。 この Red Hat の Service Registry はオンプレミス環境に導入することも可能ですが、ここでは Red Hat がマネージドサービスとして提供している Red Hat OpenShift Service Registry の使い方について説明をしていきたいと思います。 Red Hat OpenShift Service Registryの使い方 Red Hat OpenShift Service Registry を使用するには Red Hat アカウントが必要です。まだ Red Hat アカウントを持っていない場合には、 Red Hat Hybrid Cloud Console にアクセスして、Red Hat アカウントを作成しましょう。Red Hat のマネージドサービスとアカウントの作り方については、 こちらの赤帽エンジニアブログの記事 もご参照ください。アカウントを作成したら、 Red Hat Hybrid Cloud Console にログインして左の Application and Data Services メニューを選択します。さらにメニューから Service Registry をクリックすると以下のような画面が表示され、Service Registry のインスタンスを作成していくことができます。 [Create Service Registry instance] ボタンをクリックすると、Service Registry インスタンスの新規作成画面が表示されるので、任意の名前をつけて [Create] ボタンをクリックしましょう。 Service Registry のインスタンスが作成できたら、そのリンクをクリックしてAPIドキュメントやスキーマ定義などのアーティファクトをレジストリにアップロードすることができるようになります。以前ご紹介した OpenShift API Designer を使用すると、API Designer と Service Registry の連携機能を利用して API Designer で作成したアーティファクトを API Designer の画面上からこの Service Registry にインポートすることも可能です。 アーティファクトのアップロード画面は以下のようになっており、Group & ID、Type、Artifact を入力します。 Group (オプショナル): アーティファクトを管理する上でのユニークな組織やアプリケーションなどのグループ名(デフォルトでは "default" グループ) ID (オプショナル): アーティファクトを識別するためのユニークなID(明示的に指定しない場合には Service Registry によって生成されるUUID) Type (必須): Avro Schema や OpenAPI など、アーティファクトの型を指定(自動的に検出も可能) Artifact (必須): ドラッグ&ドロップやアップロード、URL などでアーティファクトのコンテンツを指定(テキスト入力エリアに直接入力も可能) 例えば、Artifact のテキスト入力エリアに以下の Avro Schema をコピー&ペーストし、[Upload] ボタンをクリックしてアップロードしてみましょう("FullName"という名前のシンプルなAvro Schemaです)。 { "type": "record", "namespace": "com.example", "name": "FullName", "fields": [ { "name": "first", "type": "string" }, { "name": "last", "type": "string" } ]} アップロードが完了すると、以下のように "FullName" という名前のAvroスキーマのメタデータ情報(ID、説明、ステータス、作成日、所有者など)が表示されます。コンテンツルールのセクションでは検証ルール(Validity rule)と互換性ルール(Compatible rule)を設定することができるようになっています。検証ルールではアーティファクトのコンテンツの有効性を検証を行うことができ、互換性ルールでは後方互換や前方互換のルールを設定して新バージョンとの互換性チェックを行うことができます。[Upload new version] ボタンをクリックして新しいバージョンのスキーマを追加することも可能です。 アーティファクトの Content タブをクリックすると、アーティファクトの中身を JSON あるいは YAML 形式で表示して確認することができるようになっています。 Service Registry へのアクセス方法 プロデューサーやコンシューマーなどのクライアントアプリケーションから Service Registry にアクセスするには、Service Registry インスタンスに接続するための URL とサービスアカウントの情報が必要となります。Service Registry の URL 情報は、Service Registry インスタンスの一覧ページで表示したいインスタンスのオプションメニューから [Connection] を選択すると以下のように表示されます。 Service Registry の API の URL として3つ表示されていますが、Service Registry を使用する新規アプリケーションを開発する場合には、通常 Core Registry API のURLを使用して Service Registry にアクセスしますので、クライアントアプリケーションではこの URL 情報が必要になります。Schema Registry compatibility API は Confluent 社の Schema Registry と互換性のある API、CNCF Schema Registry API は CNCF Schema Registry と互換性のある API の URL 情報です。 また、Service Registry と Kafka インスタンスにアクセスするには、各インスタンスへのアクセス権限を持ったサービスアカウントが必要になります。サービスアカウントがまだ作成されていない場合には、[Create service account] ボタンをクリックしてサービスアカウントを作成し、Service Registry インスタンスの [Access] タブの画面で [Grant access] ボタンをクリックしてそのサービスアカウントにアクセス権限を付与します。 以上が Red Hat OpenShift Service Registry の概要と基本的な使い方となりますが、次回は実際にクライアントアプリケーションから Service Registry にアクセスするための方法についてご紹介したいと思います。
レッドハット ソリューションアーキテクト石倉です。 この記事は、RED HAT BLOG のブログ記事、 Integrating SAP with other applications using Red Hat OpenShift の翻訳記事です。 S/4HANA を始めとする SAP 社が提供するアプリケーションのランドスケープは、グローバル企業の機能を維持するために非常に重要で、その為に高度に複雑化する傾向があることはよく知られています。 一因としては、一般的な SAP インストール ベースのアプリケーションがそもそも多数ある (ERP、BW、GRC、SCM、CRM など) だけでなく、SAP アプリケーションが通常、他の多くのサードパーティ アプリケーションとの連携が必要となり、全体として企業内で大規模なエコシステムを構成するためです。 SAP ランドスケープには、人事(HR)、財務(FI)、サプライチェーン、顧客管理(CRM)、ビジネス ウェアハウス、その他の多くの幅広いデータが含まれており、ランドスケープ全体で、これらのデータを取り扱う 1 つのアプリケーションセットになっていると言えます。 そのため、アプリケーション開発者やアーキテクトにとって、SAP ランドスケープが保持する資産(データ)とそれに接続する必要のあるアプリケーションとの間のインテグレーションを定義する場合に、管理が非常に困難になるという異質性が生じます。 上記インテグレーションを作成して利用するための従来型のアプローチは、常に ESB (Enterprise Service Bus) または EAI (Enterprise Application Integration) パターンに基づいていました。 インテグレーションプラットフォームは ESB、EAI 両方のモデルに従って作成できますが、インテグレーションが抽象化出来ない点が 1 つの大きな問題です。 これにより、各種アプリケーション向けに再利用可能なコンポーネントを実装することが非常に難しくなり、その結果、移植性も保守性も低くなり、非常にアドホックな実装になります。 SAP の歴史的なインテグレーションプラットフォームである SAP PI/PO (以前の SAP XI) は、ESB および EAI のパターンに従っています。 ESB および EAI パターンは、その性質上、クラウドベースのアプリケーションとのやり取りにはあまり適していません。 クラウドネイティブなデプロイは明らかに現在の開発スタイルのトレンドであるため、企業が自社のアプリケーションを SAP バックボーンとインテグレーションする方法を見直す必要があります。 さらに、マイクロサービスはよりモジュラーアプローチを提供するため、広く採用されています。 また、プロセスがより小さい部品に分割されるため、そのうちの 1 つに障害が発生すると、プロセス全体が停止します。 これらのことはすべて、再利用性、抽象化、一般化、および高移植性の方向性に向かっている事を示しています。 また、これらの特性は、インテグレーションのための API ファーストのアプローチ に含まれています。 API ファーストのアプローチでは、実行する必要のある関数を呼び出す API を最初に定義し、それが完了したら、実際のコードを記述します。 このモデルは、一連の API のみが提供されるため、開発者とユーザーにとって非常に便利です。 実装の詳細は抽象化されており、アプリケーションまたは記述中のコードからの呼び出しを記述するだけで済みます。 そのため、プラットフォーム間の移植性が向上するため、オンプレミスまたは最初に作成された場所とは異なるクラウドでインテグレーションを使用する場合に、再定義をする必要はありません。 2027 年までに SAP S/4HANA に移行する必要があることを考えると、API ファーストのアプローチに基づく統合プラットフォームを使用することの重要性はさらに大きくなります。 その根拠の 1 つは、"コアをクリーンに保つ" 必要があることです。 つまり、コアにカスタム コードは作成しないようにするということです (これまで SAP のバージョン アップを面倒なものにしていた原因です)。 これを実現するには、顧客はすべての独自のデプロイメント (カスタムコード) を外部に移動する必要がありますが、 (別の場所で作成された新しいデプロイメントは) SAP コアに接続してデータを交換する必要があります。 S/4 HANA への移行をまだ実行していないお客様でも、SAP からカスタム コードを抽出し (抽出してみると、ほとんどの場合、既に使用されなくなっているか、コード内に多数の無駄な部分が見つかると思います)、外部に移動することでメリットが得られます。 事前にこれらの対応を行うと、移行プロセスが大幅に高速化され、プロジェクトリスクが軽減されます。 図 1 は、SAP 向けの API マネジメントプラットフォームを作成するべき理由と、その概要図を示しています。 図1:SAPシステム向けAPIマネジメントプラットフォーム サテライト システム (周辺システム) は、SAP からのデータを使用するさまざまなアプリケーションをホストします。 また、サテライト システム (周辺システム) は、 SAP システムのデータ構造の詳細を知る必要はありません。 ソリューションの実装 Red Hat OpenShift をベースとしたインテグレーションプラットフォームにより、お客様はすべてのレガシー アプリケーションを OpenShift に移行し、Red Hat OpenShift クラスターですべての新しいデプロイを行うことができます。 これは、現在のもう 1 つのトレンドである、アプリケーション開発のモダナイゼーションの必要性とも一致しています。 Red Hat Integration は、SAP アプリケーションと非 SAP アプリケーションの統合を可能にする一連のテクノロジーを提供します。 このブログで紹介するソリューションで使用される主なテクノロジーは Red Hat Fuse です。 Red Hat Fuse は Apache Camel に基づいており、 SAP の主要なプロトコル (RFC、OData、iDocs など) が使用できる特定のコンポーネントを備えています。 また、SAP の機能とデータ構造を API として公開し、さまざまなサードパーティ アプリケーションで使用されるフォーマットと SAP で使用されるフォーマットとの間の構造変換を実行できます。 もう 1 つの主要コンポーネントは、API ゲートウェイとして機能する Red Hat 3scale API Management で、SAP と連携する必要があるアプリケーション群の単一の窓口になります。 IT ランドスケープ全体で必要なすべての API をインベントリとして保持し、Role Based Access Control (RBAC) 機能により、API の使用許可があるアプリケーションのみが API を呼び出せるようにします。 また、各 API の使用状況を追跡することもできます。 図2:Red Hat Fuse と Red Hat 3Scale によるインテグレーションイメージ API マネジメントプラットフォームのユースケースご紹介 API ベースのインテグレーションプラットフォームで実現できるいくつかのシナリオをご紹介します。 ケース 1:発注書 (PO) の詳細情報を取得するなど、SAP 内で Function を実行する必要があるアプリケーションでの利用例 アプリケーションは API ゲートウェイに接続し、API を使用して PO の詳細を取得するように要求します。 アクセスが許可されている場合、API ゲートウェイ (Red Hat 3Scale) は、アプリケーションから受け取ったパラメーターを使用して API Call をデータインテグレーションコンポーネント (Red Hat Fuse) に転送します。 データインテグレーションコンポーネントは、アプリケーションからの要求を SAP 向けのフォーマットに変換し、SAP システムへのRemote Function Call (RFC) 接続を行います。 PO を取得するプロセスは、Business API (BAPI) を実行することによってトリガーされます。 その後、情報は API ゲートウェイを介して要求元のアプリケーションに送り返されます。 図3:SAPの機能を呼び出すアプリケーション ケース 2:SAP バックエンドのデータ構造に直接アクセスする必要があるアプリケーションでの利用例 最初に、API ゲートウェイに接続し、必要な API を呼び出します。 アプリケーションが API の使用を許可されている場合、要求はデータインテグレーションに渡され、次に要求元のアプリケーションがデータにアクセスしようとしている SAP クライアントへのアクセス許可があるか、追加のセキュリティチェックを実行します(このチェックを実装するために、認証テーブルを持つ追加データベースが Red Hat Openshift にデプロイされています)。 チェック後、クエリは OData 形式に変換され、SAP に送信されます。データが取得されると、データインテグレーションは API ゲートウェイを経由して要求元のアプリケーションにデータを送り返します。 図4:SAPからデータを取り出すアプリケーション もう一つの興味深いユースケースは、SAP BW システムのビジネスインテリジェンス用データを頻繁に利用するアプリケーションです。 この種のシステムで使用されるクエリは高負荷であり、パフォーマンスの問題を引き起こす可能性があるため、SAP バックエンドの処理ボトルネックを回避するためにキャッシュ機能を実装することが良い解決策となります。 ケース 3:ソース (非SAP) とターゲット (SAP) のアプリケーション間のファイル/メッセージ操作での利用例 このシナリオではよりシンプルな実装にするために、API ゲートウェイは使用していません。 また、アプリケーションは Red Hat Integration に付属する JDBC/ODBC コネクタを使用し、Python OData ライブラリ( GitHub - SAP/python-pyodata: Enterprise-ready Python OData client )を使用して SAP システムに直接接続できるようにしています。 アプリケーションがコネクタにリクエストを送信すると、まずリクエストされたデータが Red Hat OpenShift クラスターに展開されたキャッシュデータベース(PostgreSQL)にすでに存在するかどうかを確認します。 もしそうであれば、アプリケーションに直ちにデータを送り返し、そうでなければ、クエリーは OData 形式で SAP システムに送信されます。 そして、その結果を DB に格納し、アプリケーションに送り返します。 図5:SAP BWシステムからキャッシュされたデータを使用するアプリケーション ケース 4:可搬性が低く、アドホックなファイル交換処理のレガシーバッチ操作を Red Hat Fuse に置き換える利用例 この例では、サードパーティアプリケーションや SAP は、従来のように限られたワークフローにしか使えない複雑なロジックを使用する必要はありません。 Red Hat Fuse が必要なすべてのファイル操作を処理するので、サードパーティアプリケーションや SAP は、それぞれのアプリケーション独自のフォーマットで互いにファイルを送受信することができます。 図6:Red Hat FuseのAPIを利用して変換された請求書を処理するバッチ処理の例 まとめ SAP と非 SAP アプリケーションの統合は、まだレガシー SAP システムを利用していて SAP S/4HANA への移行を準備しているお客様にとっても、すでに移行を完了したお客様にとっても、非常に重要なテーマです。 どちらの場合も、SAP バックエンドは他の多くのアプリケーションと相互に連携する必要があり、その数は時間とともに急速に増加していくため、より柔軟で俊敏な方法で展開する必要があるとともに、管理が容易であらゆる種類のインテグレーションを提供できるプラットフォームが必要とされています。 Red Hat Integration on OpenShift は、これらのシナリオを網羅的にカバーします。 SAP と非 SAP アプリケーション間のインテグレーションを実現する方法を提供し、API に基づいているため、再利用可能でメンテナンスが簡単です。 更に、SAP に接続する必要のあるすべてのアプリケーションを開発・実行するためのプラットフォームを提供し、DevOps の方法論にそったクラウドネイティブな手法やマイクロサービスの利用を可能にします。 これにより、企業はアプリケーションの開発サイクルを短縮し、より柔軟な対応が可能になります。 関連情報 https://www.redhat.com/architect/portfolio/architecturedetail?ppid=28 Red Hat Integration Red Hat OpenShift | Red Hat Red Hat Fuse Red Hat 3scale API Management 企業によるアプリケーションパフォーマンスとビジネス成果の最大化を促すRed Hat Integration
Red Hat で Java Platform Advocate として OpenJDK を担当している伊藤ちひろ( @chiroito )です。 この記事は、Red Hat Developerのブログ記事、 Java 17: What’s new in OpenJDK's container awareness | Red Hat Developer の翻訳記事です。 OpenJDKは、以前からLinuxコンテナ(DockerやPodman、またKubernetesのようなコンテナオーケストレーションフレームワークなど)を意識してきました。コンテナ対応とは、OpenJDKがコンテナ内で動作していることを検出することを意味します。この記事では、コンテナ対応がなぜ有用なのか、OpenJDKのその領域で最近何が変わったのか、そして、開発者がJVMの設定決定方法について理解を深めるために利用できる診断オプションは何かについて学びます。 OpenJDKのコンテナの検出は、Linuxのコントロールグループ(cgroup)ファイルシステムを使用して、強制されたリソース割当てを検出します。この記事を書いている時点では、Java 17 と 11.0.16+ が、cgroups v1 と cgroups v2 の両方の構成をサポートする唯一の長期サポートリリースです。 OpenJDKは、コンテナで実行されているときに、特定のリソース割り当てがあるかどうかを検出し、ある場合は、その操作のためにそれらの上限値を使用します。これらのリソース制限は、例えば、JVMによって選択されたガベージコレクション(GC)アルゴリズム、ヒープのデフォルトサイズ、スレッドプールのサイズ、およびForkJoinPoolのデフォルト並列性がどのように決定されるかに影響を及ぼします。 OpenJDKのコンテナ対応機能は、Java 17とJava 11ではそれぞれの一般提供版(GA)リリースから、Java 8uではアップデート8u202から利用できます。 コンテナへの配慮が重要な理由 Kubernetesやその他多くの一般的なクラウドオーケストレーションシステムでは、デプロイ時にCPUや メモリの割り当てを行い、コンテナリソースを制限できます。これらの制限は、コンテナのデプロイ時にコンテナエンジンに渡されるオプションに変換されます。コンテナエンジンのオプションは、Linux の cgroup 疑似ファイルシステムを通じて、リソースの制限を設定します。Linux カーネルは、cgroup を介してリソース制限が設定されている場合、どのプロセスもその制限を超えないようにします(少なくとも、長時間は超えない)。 このような環境でJavaプロセスをデプロイすると、デプロイされたプロセスに対してcgroup limitが設定されることがあります。Java仮想マシンが設定されたcgroupの制限を考慮しない場合、オペレーティングシステムが提供しようとする以上のリソースを消費する危険性があります。その結果、Java プロセスが予期せず終了する恐れがあります。 OpenJDKのコンテナ対応コードに関する最近の変更点 Java 11とJava 17の間では、cgroups v2のサポートとOperatingSystemMXBeanにおけるコンテナの対応という、最も顕著な2つの追加事項があります。 cgroups v2 サポート Java 15 以降、OpenJDK は使用中の cgroup バージョンを検出し、cgroup バージョン固有の設定にしたがって制限を検出します。Java 15 以降では、OpenJDK は cgroups v1 に加えて cgroups v2 または単一階層構造(Unified Hierarchy)をサポートしています (これについては JDK-8230305 を参照してください)。 cgroups v2 のみを持つシステムで Java 11 または Java 8 を実行すると、コンテナの検出が行われず、代わりにホストの値が使用されます。先に説明したように、これはコンテナ化されたデプロイメントで予期しないアプリケーションの動作をもたらす可能性があります。 システムで使用されているcgroupのバージョンを表示する簡単な方法の1つは、JVMオプションの -XshowSettings:system オプションです。(このオプションは Linux 固有のものです。) 以下はその例です。 $ java -XshowSettings:system -version Operating System Metrics: Provider: cgroupv2 Effective CPU Count: 2 CPU Period: 100000us CPU Quota: 200000us CPU Shares: 1024us List of Processors: N/A List of Effective Processors, 4 total: 0 1 2 3 List of Memory Nodes: N/A List of Available Memory Nodes, 1 total: 0 Memory Limit: 1.00G Memory Soft Limit: 800.00M Memory & Swap Limit: 1.00G openjdk version "17.0.2" 2022-01-18 OpenJDK Runtime Environment 21.9 (build 17.0.2+8) OpenJDK 64-Bit Server VM 21.9 (build 17.0.2+8, mixed mode, sharing) 使用中のcgroupの構成を把握する他の方法としては、 VM.info jcmd ユーティリティ (in section "container (cgroup) information") や -Xlog:os+container=debug JVMオプションがあります。 cgroup v2がサポートされていない場合、例えばJava 11を使用しているのであれば、 -XshowSettings:system の出力は次のようなものになります。 $ java -XshowSettings:system -version Operating System Metrics: No metrics available for this platform openjdk version "11.0.14" 2022-01-18 OpenJDK Runtime Environment 18.9 (build 11.0.14+9) OpenJDK 64-Bit Server VM 18.9 (build 11.0.14+9, mixed mode, sharing) システム・メトリクスが検出されない場合、JVMプロセスはホストOSの設定を使用します。 OperatingSystemMXBean コンテナ対応 Java 14 以降、OperatingSystemMXBean は、JDK の内部で Linux 固有の Metrics Java API を使用してシステム情報を通知するようになりました。つまり、cgroup 制限が設定されている場合、OperatingSystemMXBean はコンテナ・ホストのシステム・リソース上でそれらの制限を(必要に応じて)報告します。この機能は、Java 8(8u272 以降)および Java 11(11.0.9 以降)にもバックポートされています。 注意 : OpenJDKでは、 -XX:-UseContainerSupport JVMオプションで、コンテナ対応機能を無効化できます。これにより、 OperatingSystemMXBean のコンテナ対応を無効にします。 次のファイル CheckOperatingSystemMXBean.java は、実行中のシステムに関する情報を表示します。コンテナに対応した OperatingSystemMXBean を使用しているので、実行する環境に応じて物理ホストまたはコンテナのリソースの情報を表示します。 import com.sun.management.OperatingSystemMXBean; import java.lang.management.ManagementFactory; public class CheckOperatingSystemMXBean { public static void main(String[] args) { System.out.println( "Checking OperatingSystemMXBean" ); OperatingSystemMXBean osBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); System.out.println(String.format( "Runtime.availableProcessors: %d" , Runtime.getRuntime().availableProcessors())); System.out.println(String.format( "OperatingSystemMXBean.getAvailableProcessors: %d" , osBean.getAvailableProcessors())); System.out.println(String.format( "OperatingSystemMXBean.getTotalPhysicalMemorySize: %d" , osBean.getTotalPhysicalMemorySize())); System.out.println(String.format( "OperatingSystemMXBean.getFreePhysicalMemorySize: %d" , osBean.getFreePhysicalMemorySize())); System.out.println(String.format( "OperatingSystemMXBean.getTotalSwapSpaceSize: %d" , osBean.getTotalSwapSpaceSize())); System.out.println(String.format( "OperatingSystemMXBean.getFreeSwapSpaceSize: %d" , osBean.getFreeSwapSpaceSize())); System.out.println(String.format( "OperatingSystemMXBean.getSystemCpuLoad: %f" , osBean.getSystemCpuLoad())); } } プログラムをコンパイルして実行すると、そのプログラムが利用できるリソースが表示されます。 $ ./jdk-17.0.1+12/bin/javac CheckOperatingSystemMXBean.java $ sudo podman run -ti --rm --memory 300m --memory-swap 300m --cpu-period 100000 --cpu-quota 200000 -v $(pwd):/opt:z fedora:35 [root@7a0de39d8430 opt]# /opt/jdk-17.0.1+12/bin/java CheckOperatingSystemMXBean Checking OperatingSystemMXBean Runtime.availableProcessors: 2 OperatingSystemMXBean.getAvailableProcessors: 2 OperatingSystemMXBean.getTotalPhysicalMemorySize: 314572800 OperatingSystemMXBean.getFreePhysicalMemorySize: 291680256 OperatingSystemMXBean.getTotalSwapSpaceSize: 0 OperatingSystemMXBean.getFreeSwapSpaceSize: 0 OperatingSystemMXBean.getSystemCpuLoad: 0.050386 [root@7a0de39d8430 opt]# /opt/jdk-17.0.1+12/bin/java -XX:-UseContainerSupport CheckOperatingSystemMXBean Checking OperatingSystemMXBean Runtime.availableProcessors: 4 OperatingSystemMXBean.getAvailableProcessors: 4 OperatingSystemMXBean.getTotalPhysicalMemorySize: 5028548608 OperatingSystemMXBean.getFreePhysicalMemorySize: 3474866176 OperatingSystemMXBean.getTotalSwapSpaceSize: 5027917824 OperatingSystemMXBean.getFreeSwapSpaceSize: 5027917824 OperatingSystemMXBean.getSystemCpuLoad: 0.000000 コンテナのデフォルトをチューニング 場合によっては、メモリとCPU使用率に関するOpenJDKのデフォルト設定が、コンテナで実行されるアプリケーションにとって望ましい設定ではない可能性があります。OpenJDKは、マルチユーザのデスクトップとサーバシステム、およびコンテナの使用ケースなどを考慮する必要があります。コンテナはデスクトップや サーバとは異なり、そのコンテナでは Java プロセスだけが実行されることがかなり多いからです。たとえば、Kubernetesコンテナで、800MB RAMのメモリ制限を持つ場合、デフォルトの -XX:MaxRAMPercentage=25 は、おそらく、マルチユーザのデスクトップシステムでのそれほどの意味を持ちません。なぜなら、最大ヒープサイズは、その800MBコンテナの200MB RAM(800MBの4分の1)により、上限に達してしまうからです。 OpenJDK を典型的なコンテナ用途に合わせるために、 JDK-8186248 (および OpenJDK 8u では JDK-8146115 ) で導入されたオプションで、特にこの特定の用途に合うように、ヒープサイズを利用可能(コンテナ)メモリに対するパーセントで設定できます。これらのオプションは、 -XX:InitialRAMPercentage , -XX:MaxRAMPercentage , そして -XX:MinRAMPercentage です。 アプリケーションがコンテナ内で動作している場合、これらのパーセントオプションを設定することは、 -Xmx と -Xms を使用してアプリケーションの最大ヒープサイズと最小ヒープサイズを設定するよりも望ましいことです。これらの -XX のオプションは、コンテナのメモリ制限に対してヒープサイズを設定し、デプロイメント設定でメモリ制限が変更されると、再デプロイメント時に自動的に更新されます。両方の設定がある場合、 -Xmx と -Xms が優先されます。 コンテナ内で実行するときにCPU設定を上書きするためのもう一つの重要なオプションは、 -XX:ActiveProcessorCount です。このオプションは、コンテナ検出機構に関係なく、JVMが使用すべきCPUコアの数を正確に指定できます。 コンテナ対応のサポートは -XX:-UseContainerSupport オプションで完全に無効にすることもできます。 表1は、コンテナで実行する際にJVMの設定を調整するための便利なオプションをまとめたものです。 表1:チューニングのオプション JVMオプション JVMオプションの置き換え 説明 デフォルト値 -XX:InitialRAMPercentage -XX:InitialRAMFraction 初期ヒープサイズに使用される実メモリのパーセンテージ 1.5625 -XX:MaxRAMPercentage -XX:MaxRAMFraction 最大ヒープサイズに対する実メモリの最大使用率 25 -XX:MinRAMPercentage -XX:MinRAMFraction 物理メモリが小さいシステムで、最大ヒープサイズに使用される実メモリの最小パーセンテージ 50 -XX:ActiveProcessorCount n/a 有効として報告すべきVM が使えるCPU 数 n/a -XX:±UseContainerSupport n/a コンテナ検出と実行時設定のサポートを有効にする true 意固地なオプション CPUリソース制限のためのデフォルトのコンテナ検出法(ヒューリスティック)は、一般的なコンテナオーケストレーションフレームワーク(特にKubernetesとMesos)がコンテナを起動する方法を主に手本にしている。たとえば、Kubernetesのセットアップでは、CPUリソースの制限がある場合に考慮すべき4つの(主要な)ケースがあります。実際には、クラスタのデフォルトが設定されている場合もあるため、さらに多くの可能性がありますが、それらもこれらのケースでほぼカバーされます。 spec.containers[].resources.limits.cpu と spec.containers[].resources.requests.cpu の両方が明示的に設定されています。 spec.containers[].resources.limits.cpu のみが明示的に設定されている。Kubernetes は spec.containers[].resources.requests.cpu を spec.containers[].resources.limits.cpu と同じ値に設定します。 spec.containers[].resources.requests.cpu のみが明示的に設定されている。Kubernetes は spec.containers[].resources.limits.cpu を spec.containers[].resources.requests.cpu 以上に設定します。 spec.containers[].resources.limits.cpu と spec.containers[].resources.requests.cpu のどちらも設定されていない。Kubernetes は spec.containers[].resources.limits.cpu を未設定のままにし、他にデフォルトがない場合はコンテナの CpuShares 値を 2 に設定します。 コンテナ・オーケストレーション・フレームワークは通常、 spec.containers[].resources.requests.cpu のミリコア値を1024倍して、DockerやPodmanの --cpu-shares コマンドラインオプションで設定した値に直接変換しています。したがって、JVMはこの知識に基づいて、CPUシェアの値を計算します。 さらに、JVMはCPUコアの値の下限を1に設定します。つまり、 spec.containers[].resources.requests.cpu=500m の設定を持つコンテナは、JVMがシングルCPUコアを使用するようになります( 0.5 * 1024 = 512 は --cpu-shares=512 のオプションを生成しますが、 cpu-shares < 1024 では1コアとなります)。spec.containers[].resources.requests.cpu=2` と設定すると、JVMは2つのCPUコアを使用するようになり、以下同様です。 これらのルールは、先ほどの一覧の最後のケースで、どちらのオプションも明示的に設定されていない場合、JVMが1つのCPUコアのみを使用できると考えるようになることに注意してください。 このような場合は、 -XX:ActiveProcessorCount で希望のCPUコアの値をオーバーライドすることをお勧めします。 注意 : バージョン 18.0.2+, 17.0.5+, 11.0.17+ において、OpenJDK は利用可能な CPU コアの計算に CPU シェアの設定を考慮しなくなりました。詳しくは、 JDK-8281181 を参照してください。 spec.containers[].resources.limits.cpu (L) millicore値は、DockerとPodmanの --cpu-quota (Q) と --cpu-period (P) 値に直接変換します。JVMは、(L)で設定された制限を ceil(Q/P) に基づいて計算します。もし、 spec.containers[].resources.limits.cpu と spec.containers[].resources.requests.cpu の両方が指定された場合、 limits の値が優先されることに注意しましょう。これにより、最初の3つのケースでは、JVMはCPUコアに合理的な値を使用するようになります。CPU割り当てよりもsharesを優先したい場合は、 -XX:-PreferContainerQuotaForCPUCount`オプションを指定します(これについては JDK-8197867 を参照してください)。 同様に、RAMのリソース制限もコンテナ・オーケストレーション・フレームワークによって存在します。 spec.containers[].resources.limits.memory は、コンテナエンジンの -memory と -memory-swap コマンドラインオプションに変換されます。 spec.containers[].resources.requests.memory は通常、生成されたコンテナに影響を与えません。cgroup v2 を使用しているノードでは、 memory.min または memory.low が適切に設定されるかもしれません。メモリ要求の設定は、診断のためにそれらの値を報告する以外、JVM側には何の影響も与えません。 デバッグのための診断オプション トレースログは、OpenJDK のコンテナ対応のロジックが何を行っているかをより理解するのに非常に役立ちます。そのロジックには、2つの異なる実装があることに注意してください。ひとつはJVM ( libjvm.so ) 用で、もうひとつはコア・ライブラリーを使用するためにJavaで実装されています。 JVMのコンテナ検出ロジックは、Unified Loggingフレームワークと統合されており、例えば -Xlog:os+container=trace によって追跡できます。OpenJDK 8u JVMでは、 -XX:+UnlockDiagnosticVMOptions -XX:+PrintContainerInfo がおおよその同等のものです。これらの追跡は、デプロイされたアプリケーションのcgroup疑似ファイルシステムを検査することによって、コンテナ対応が実際に機能しているかどうか、そして、JVMがどのような値を決定しているかを出力します。 Java 11+では、どのGCが使用されているかを知ることも有用で、 -Xlog:gc=info でこの情報を表示できます。例えば、コンテナの制限で1つのCPUしか使用できない場合、Serial GCが選択されます。複数の CPU が有効で、十分なメモリ(少なくとも 2GB)がコンテナに割り当てられている場合、 Java 11 およびそれ以降のバージョンでは G1 GC が選択されます。 $ java -XX:ActiveProcessorCount=1 -Xlog:gc=info -version [0.048s][info][gc] Using Serial openjdk version "17.0.1" 2021-10-19 OpenJDK Runtime Environment 21.9 (build 17.0.1+12) OpenJDK 64-Bit Server VM 21.9 (build 17.0.1+12, mixed mode, sharing) $ java -Xlog:gc=info -version [0.006s][info][gc] Using G1 openjdk version "17.0.1" 2021-10-19 OpenJDK Runtime Environment 21.9 (build 17.0.1+12) OpenJDK 64-Bit Server VM 21.9 (build 17.0.1+12, mixed mode, sharing) その他のオプションとして、 VM.info jcmd ユーティリティがあります。これは、すでに実行されている Java プロセスのコンテナに関する設定を確認するために便利です。 VM.info で表示される cgroup 情報は、JVM がクラッシュしたときに hs_err*.log ファイルに記録される情報と同じものです。 注意 : これらの情報のほとんどは、問題を診断するための強力なツールである JDK Flight Recorder (JFR)を介して入手することもできます。コンテナでは、JFRの情報を Cryostat 経由でアクセスできます。 まとめ Kubernetesのデプロイメント設定が、cgroups v2システム上でOpenJDKがGCアルゴリズムを選択する際にどのような影響を及ぼすかについての詳細は、このトピックに関する私の動画をご覧ください。 youtu.be この動画は、コンテナへのアプリケーションのデプロイメントによって、OpenJDKの動作が若干異なる可能性があることを実証しています。この記事の助けを借りて、その理由をより理解することができます。あなたのアプリケーションのコンテナ設定を自由に調整し、クラウドへのデプロイメントを最大限に活用できるようにしましょう。cgroup v2 のサポートは、Java 17+ および OpenJDK 11.0.16+ でのみ利用可能であることに留意してください。 コンテナベース環境での Java の微調整についてもっと知りたい方は、このシリーズの最初の記事「 シングルコアのコンテナにおけるJavaのベストプラクティス - 赤帽エンジニアブログ 」を読むか、 赤帽エンジニアブログの最近の Cryostatに関するシリーズ をチェックしてください。また、, DevNation Tech TalkでChristine FloodとEdson Yanagaの「 Java and containers: What's there to think about? 」もごらん下さい
Red Hat で Java Platform Advocate として OpenJDK を担当している伊藤ちひろ( @chiroito )です。 この記事は、Red Hat Developerのブログ記事、 Best practices for Java in single-core containers | Red Hat Developer の翻訳記事です。 コンテナで動作するJavaアプリケーションの数が増えています。コンテナの採用は、特定のチームや企業の市場区分や クラウドの成熟度によるので、正確な数を決定するのは困難です。たとえば、New Relic のデータでは、顧客の Java ワークロードの 62% 以上がコンテナで実行されています。他のデータと同様、このデータも市場全体に対する代用として不完全なものです。しかし、このレポートは、Java市場の重要な一部分がすでにコンテナベースの環境に移行していることを証明しています。また、こうした移行の流れがまだ終わっていないことも、さまざまなデータからうかがい知れます。 Javaを使用しているチームは、コンテナベースの導入におけるいくつかの側面に特別な注意を払い、いくつかのベストプラクティスを採用する必要があります。この記事では、ガベージコレクタ(GC)の選択と、利用可能な CPU数とメモリに基づくデフォルトの選択方法に焦点を当てます。 Javaアプリケーションのライフサイクル 従来のJavaアプリケーションのライフサイクルは、いくつかのフェーズで構成されています。起動、大量のクラス読み込み、ジャストインタイム(JIT)コンパイルによるウォームアップ、そして、クラス読み込みやJITが比較的少ない数日から数週間続く長い持続的状態が続きます。これは、Javaがサーバーサイドの技術としてスタートし、JVMがデータセンターのオンプレ上で動作していた時代を思い出せば、納得がいきます。 その世界では、クラスタの拡張には物理マシンを追加注文してデータセンターに配送してもらう必要があり、アプリケーションのバージョンアップはおそらく数カ月ごとに行われ、アプリケーションプロセスは週または月単位で稼働時間を測定していました。Javaアプリケーションの拡張性は、通常、スケールアップに焦点が当てられており、大量のメモリを搭載した大規模なマルチコアマシン上でJavaを効率的に動作させることが目標でした。 このようなJavaアプリケーションの導入モデルは、いくつかの異なるが関連する方法が、クラウド導入の課題となっています。 コンテナの寿命はもっと短いかもしれない(場合によっては数秒)。 クラスタサイズは動的に再調整または再構成されるかもしれません(例:Kubernetes)。 マイクロサービスアーキテクチャは、プロセスサイズが小さくなり、寿命が短くなる傾向があります。 これらのことから、多くの開発者は、Javaアプリケーションをコンテナに移行する際、できるだけ小さなコンテナを使用しようとします。クラウドベースのアプリケーションは通常、使用するRAMとCPUの量によって課金されるため、これは理にかなっています。 しかし、ここには、Javaの専門家ではないエンジニアにはわからない微妙なニュアンスがあります。詳しく見ていきましょう。 動的な実行プラットフォームとしてのJVM JVMは、それが実行されているマシンの実情に合わせて、起動時に特定の重要なパラメータを設定する、非常に動的なプラットフォームです。これらのプロパティには、JVMが認識するCPUの数と種類、利用可能な物理メモリが含まれます。実行中のアプリケーションの動作は、異なるサイズのマシンで実行された場合には、異なるものになる可能性がありますし、それはコンテナにも当てはまります。 JVMが起動時に確認する動的なプロパティには、次のようなものがあります。 JVM組み込み関数:特定のCPU機能(ベクトルサポート、SIMDなど)に依存する、パフォーマンスが重要なメソッドの独自にチューニングされた実装 内部スレッドプール(共通プールなど)のサイズ GCに使用するスレッド数 この一覧だけでも、コンテナイメージに必要なリソースの定義を誤ると、GCや 一般的なスレッド操作に関連する問題が発生することがわかります。 しかし、問題は根本的にこれより深いのです。Java 17を含む現在のJavaのバージョンは、GCがコマンドラインで明示的に指定されていない場合、いくつかの動的チェックを実行し、エルゴノミクスの観点から(自動的に)使用するGCを決定します。 この原因を突き止めるために、OpenJDKのソースコードを見てみましょう。具体的には、 src/hotspot/share/gc/shared/gcConfig.cpp ファイルに GCConfig::select_gc() という C++ メソッドがあり、GC が明示的に選ばれない限り GCConfig::select_gc_ergonomically() が呼ばれることが分かります。このメソッドのコードは void GCConfig:: select_gc_ergonomically () { if (os:: is_server_class_machine ()) { #if INCLUDE_G1GC FLAG_SET_ERGO_IF_DEFAULT (UseG1GC, true ); #elif INCLUDE_PARALLELGC FLAG_SET_ERGO_IF_DEFAULT (UseParallelGC, true ); #elif INCLUDE_SERIALGC FLAG_SET_ERGO_IF_DEFAULT (UseSerialGC, true ); #endif } else { #if INCLUDE_SERIALGC FLAG_SET_ERGO_IF_DEFAULT (UseSerialGC, true ); #endif } } このコード片の意味は、C++マクロ(Hotspotのソースのいたるところで使われている)によって多少不明瞭になっていますが、基本的には次のようなことに集約されます。Java 11と17では、コレクタを指定しなかった場合、次のルールが適用されます。 マシンがサーバクラスである場合、GC として G1 を選択します。 マシンがサーバクラスでない場合、GC として Serial を選択します。 マシンがサーバークラスかどうかを判断するHotspotのメソッドは os::is_server_class_machine() です。これのコードを見てみると // これがサーバークラスのマシンの動作定義です。 // 物理CPUが2つ以上、メモリが2GB以上あること これは、Java アプリケーションが、CPU が 2 つ未満、メモリが 2GB 未満と思われるマシンまたはコンテナで実行される場合、デプロイ時に特定の GC アルゴリズムを明示的に選択しない限り、Serial アルゴリズムが使用されることを意味します。この結果は、通常、G1よりもStop-the-World(STW)ポーズ時間が長くなるため、チームが望むものではありません。 この効果を実際に見てみましょう。アプリケーションの例として、AmazonのHeapothesysプロジェクトの一部であるHyperAllocを使用することにします。このベンチマークツールは、"ガベージコレクタの待機時間に影響を与える基本的なアプリケーション特性を模した、人工的な負荷 "です。 簡単なDockerfileからコンテナイメージを起動します。 FROM docker.io/eclipse-temurin:17 RUN mkdir /app COPY docker_fs/ /app WORKDIR /app CMD [ "java" , "-Xmx1G" , "-XX:StartFlightRecording=duration=60s,filename=hyperalloc.jfr" , "-jar" , "HyperAlloc-1.0.jar" , "-a" , "128" , "-h" , "1024" , "-d" , "60" ] 使用しているHyperAllocのパラメータは、ヒープサイズが1GB、シミュレーションの実行時間が60秒、割り当て速度が128MB/秒です。これは、1つのCPUで使用するイメージです。 また、2つのCPUのコンテナで使用するために、割り当て速度を256MB/秒にした以外は同一のイメージも作成します。2番目のケースで高い割り当て率を設定したのは、HyperAllocで利用可能なCPUの量を増やして、両方のバージョンで同じ割り当て負荷を経験させるためです。 JDK Flight Recorder (JFR)を使うと、全期間のログを取得できます。それは60秒と非常に短いですが、この単純な例でJVMの全体的な動作を実証するのに十分な時間をもたらします。 2つのケースを比較しています。 1 CPU, 2GB イメージで 128MB の割り当て速度 (Serial GC) 2 CPUs, 2GB イメージで 256MB の割り当て速度 (G1 GC) 私たちが見たい2つのGCデータは、別々のケースの一時停止時間とGCの処理能力(GCを実行するために費やされた総CPUとして表現さ れる)です。 このサンプルを使って自分のデータで実験したい場合は、JFR HacksのGitHubリポジトリにコードがあります。このプロジェクトはGunnar MorlingのJFR Analyticsに依存しており、JFRの記録ファイルを照会するためのSQL風のインターフェイスを提供しています。 注意 : この後のグラフでは、実行のタイムスタンプはVM起動後のミリ秒に正規化されています。 まず、一時停止時間から見ていきましょう。図1は、2つのケースの合計休止時間を示しています。 図1:1CPUの実行は、2CPUの実行に比べて、休止時間の合計が非常に多い。 この結果は、G1 を使用することの明確な利点を示しています。すべてのコレクションで、一時停止時間が大幅に短縮されました。G1Newコレクションは、シリアルの若い領域のGC(DefNewとして知られている)よりも 短いです。しかし、G1NewのコレクションはDefNewの3倍近くあります。 この急増の理由は、若い領域のGCは常に完全にSTWであることです。なぜなら、割り当てスレッド(アプリケーションのスレッド)は高いまたは予測できない割り当て速度を持つ傾向があります。これは、GCスレッドと割り当てスレッド間のCPUの競争は必ず勝てる訳ではないことを意味します-若い領域のGCのSTWポーズを受け入れ、できるだけ短く保つ方がよいでしょう。 G1 は「全部実行するか、あるいは実行しないか(all or nothing)」のコレクタではありません。その動作は領域に基づいているため、現在の割り当て速度に先んじるためにいくつかの若い領域を収集し、その後アプリケーションスレッドを再起動し、より短い休止の数を増やします。このような取引関係(トレードオフ)の全体的な効果については、後ほど詳しく説明します。 古い領域のGCでは、その効果はさらに顕著です。G1Old は実際に総休止時間が落ち込みますが、SerialOld は古い領域のGCで明らかに急増します。これは、G1Oldが並行コレクターであるため、コレクションの実行時間の大部分において、アプリケーションスレッドと並行して実行されているためです。2CPUの例では、G1Oldが実行されている間、1CPUはGCに、1CPUはアプリケーションスレッドに使用されています。 図2は、各コレクションの実行経過時間を示し、総停止時間との対比で示したものである。これは、G1Oldの並行性を示しています。 図2:1CPUの実行は、2CPUの実行に比べてGCに費やす時間が非常に長い 案の定、G1Oldの総休止時間が、経過時間ごとで見ると最大になっています。また、G1でもSerialでも、古い領域のGCの数はほぼ同じであることが目視で確認できます。 この時点で問われるかもしれない、ひとつの明白な疑問があります。GCの実行による全体的なコストは、CPU時間でどのくらいでしょうか? 若い領域のG1コレクションが若い領域のSerialコレクションよりも多いため、G1が使用する全体的なCPU時間はより高いという可能性はありませんか?この質問に答えるために、2つのコレクターのGCに費やされた累積時間を示す図3を見てください。 図3:2CPUの実行は、1CPUの実行よりも全体としてGCに費やす時間が長いが、2倍の時間はかからない。 一見すると、G1がSerialよりもCPUを多く使っているように見えます。しかし、G1実行は2つのCPUを使用し、2倍の割り当て率で処理していることを忘れてはいけません。つまり、CPU単位、あるいは割り当てGB単位で見ると、G1はSerialよりもまだ効率が良いということです。 全体として、より小さなコンテナの見かけ上の魅力にもかかわらず、ほとんどすべての場合において、2つのCPUと2GBのメモリを持つコンテナでJavaプロセスを実行し、G1の並行GCに利用可能なリソースを利用させる方が良いということです。 まとめと今後について このおもちゃの例で効果がはっきり分かったところで、1つ大きな疑問が残りました。この効果は、本番用のコンテナではどのように発揮されるのでしょうか? 答えは、Javaのバージョンとカーネルのサポートに依存します。特に、cgroupsと呼ばれる特定のカーネルAPIがv1かv2のどちらであるかによります。 このシリーズの第2回では、Severin Gehwolfが、Hotspotがどのようにコンテナのプロパティを検出し、それに基づいて自動サイズを決定するのか、その詳細を深く掘り下げて説明します。また、Javaアプリケーションのコンテナ化に関するMicrosoftの最近の記事もご覧になってみてください。
Red Hatの平です。 日経Linuxの最新号である2022年11月号 が発売されました。今回は特集記事で「Red Hat Enterprise Linux 9 入門」があります。 なんと、日経Linuxの編集部の方が書いてくれました。ありがたや、ありがたや。 Red Hat Developer Subscription for Individualsの解説、RHEL9のインストール方法がフルカラーで分かりやすく解説されていました。Red Hat Developer Subscription for Individualsは、個人のソフトウェア開発者向けにRHELを最大16本まで無償でご利用頂けるプログラムです。 ご自宅のパソコンで趣味のプログラミングするLinux環境に使っても頂いても無償です。RHELであれば各種GPUやFPGAのドライバーが概ね使えますのでAIや機械学習のようなワークロードの開発作業にも適用できます。 書店で見かけた際には、ぜひ手にとってレジまで持っていってください。
Red Hatの森若です。 今日は /var/tmp にプログラムを置いて使うのはまずいという話です。 うちの /var/tmp/hoge.py 動いてるよ? systemd-tmpfiles による削除 SELinuxによる実行拒否 まとめ うちの /var/tmp/hoge.py 動いてるよ? 日常的な操作の中で特にひっかかることもなく /var/tmp/hoge.py のようなスクリプトを作成して実行できます。 そのため「標準的なディレクトリじゃないから『お作法としてよくない』って話でしょ」と思われる方もいるかもしれません。 しかしファイルシステム上のパスによって決まった動作をする仕組みがいくつかあるので、実際上の問題が懸念されます。 systemd-tmpfiles による削除 systemd-tmpfiles は設定に従ってファイルの作成や削除を行うプログラムです。 systemd-tmpfiles の設定内容は各パッケージが追加できるため、特に設定を変更していなくてもシステムにより異なります。 現在の設定を取得するには systemd-tmpfiles --cat-config とします。systemd-tmpfilesの設定は1行ずつ独立したフォーマットなので、grepなどで関連する項目を搾り込めます。 $ systemd-tmpfiles --cat-config | grep /var/tmp R /var/tmp/dnf*/locks/* R! /var/tmp/ostree-unlock-ovl.* x /var/tmp/systemd-private-%b-* X /var/tmp/systemd-private-%b-*/tmp R! /var/tmp/systemd-private-* q /var/tmp 1777 root root 30d 最後の q /var/tmp 1777 root root 30d という行が /var/tmp についての設定です。 man systemd-tmpfiles から辿って man tmpfiles.d を見ると設定内容を読みとけます。 q は、 ディレクトリを作成することを示しています。後述のクリーンアップではディレクトリそのものではなくディレクトリ以下にあるファイルやディレクトリ等が削除対象になります。 /var/tmp 1777 root root は作成するディレクトリの名前とパーミッション設定とUID, GIDです。 30d は、30日以上アクセスされていないファイルやディレクトリがクリーンアップ対象となることを意味します。 systemd-tmpfiles によるファイル作成はシステム起動直後に実施します。 systemd-tmpfiles によるクリーンアップはシステム起動後15分経過したタイミングと、その後1日おきに実施されます。 /var/tmp/hoge.py が起動時に1回実行されるだけだとすると、30日経過後のクリーンアップで削除されます。 システム構築時には「プログラムにアクセスせずに30日以上経過する」ことは稀でしょうから、気付きにくい問題と言っていいかと思います。 SELinuxによる実行拒否 SELinuxのtargeted policyが有効な場合、 /var/tmp/ 以下に作成されたファイルは tmp_t または user_tmp_t になります。targeted policyでは対話的に利用しているユーザによる操作はほぼ制限されませんので気付きにくいですが、systemdからサービスとして呼び出すような利用を行うと実行に失敗します。 タイプがuser_tmp_tになっています。 $ ls -lZ /var/tmp/hoge.py -rwxr-xr-x. 1 moriwaka moriwaka unconfined_u:object_r:user_tmp_t:s0 34 Oct 5 14:06 /var/tmp/hoge.py 一般のユーザとして実行すると成功します。 $ /var/tmp/hoge.py hoge systemdから実行させるため systemd-run で実行を指示します。 $ systemd-run /var/tmp/hoge.py Running as unit: run-u11294.service ログを見るとPermission deniedで失敗したことがわかります。 $ journalctl -u run-u11294.service Oct 05 14:06:27 turtle systemd[1]: Started run-u11294.service - /var/tmp/hoge.py. Oct 05 14:06:27 turtle systemd[994555]: run-u11294.service: Failed to locate executable /var/tmp/hoge.py: Permission denied Oct 05 14:06:27 turtle systemd[994555]: run-u11294.service: Failed at step EXEC spawning /var/tmp/hoge.py: Permission denied Oct 05 14:06:27 turtle systemd[1]: run-u11294.service: Main process exited, code=exited, status=203/EXEC Oct 05 14:06:27 turtle systemd[1]: run-u11294.service: Failed with result 'exit-code'. こちらは明確に失敗しますから、問題自体には簡単に気付けます。 systemdがuser_tmp_tのファイルを実行しようとして失敗するので、 journalctl _PID=994555 のように検索するとSELinuxが実行を拒否しているログを発見できます。 Oct 05 14:06:27 turtle audit[994555]: AVC avc: denied { execute } for pid=994555 comm="(hoge.py)" name="hoge.py" dev="dm-0" ino=9766447 scontext=system_u:system_r:init_t:s0 tcontext=unconfined_u:object_r:user_tmp_t:s0 tclass=file permissive=0 まとめ /var/tmpにプログラムを配置すると、 systemd-tmpfilesによるファイル削除やSELinuxによる実行拒否の問題があります。 /usr/local/bin または /opt/ 以下に置きましょう。
Red Hat で Java Platform Advocate として OpenJDK を担当している伊藤ちひろ( @chiroito )です。 この記事は、Red Hat Developerのブログ記事、 My advice for updating use of the Docker Hub OpenJDK image | Red Hat Developer の翻訳記事です。 コンテナ内のJava実行環境は、今後数カ月でアップデートを受けられなくなる可能性があります。そろそろ手を打つべきでしょう。この記事では、この問題を引き起こした原因である決定事項を説明し、解決策を提案します。 OpenJDK と Java SE のアップデート OpenJDKは、Java Platform, Standard Edition (Java SE)のオープンソース実装で、複数の企業やコントリビューターが共同で開発しています。 OpenJDKのプロジェクトは、Java SE仕様の各新機能リリースを表しています。機能およびセキュリティの修正を含む、それらの機能に対するその後の更新は、 JDKアップデートプロジェクト で働く保守開発者が主導しています。Java SE 8(2014年3月以降)、Java SE 11(2018年9月以降)、Java SE 17(2021年9月以降)などの長期サポートリリースは、リーダーとなる保守開発者の指揮のもと、四半期ごとにリリース更新が行われます。 リポジトリの保守開発者の役割 は、リリースを公開する際に必要かつ適切なアップデートが行われるようにすることです。このようなアップデートの際には、複数の協力者の意見を考慮して決定します。Java SEの多くのベンダーとディストリビュータは、その後、OpenJDKのソースコードから構築して、独自のブランドのJava SE製品の新しいリリースを提供します。 Andrew Haley (Red Hat) は Java 8 アップデートと Java 11 アップデートのリーダで、Goetz Lindenmaier (SAP) は Java 17 アップデートのリーダである。Java SEをベースにしたOpenJDKの商用サポート付きディストリビューションを提供している企業に所属するアップデートの保守開発者は、このプロジェクトに独立した協力者として働いています。 Docker HubがOpenJDKイメージの廃止を決定 長年、Docker Hubの 公式イメージを構築する人たちは、 Eclipse Adoptium などからOpenJDK Java SEアップデートバイナリを取り出し、独自のイメージを構築していました。しかし、2022年7月、Docker Hubのイメージ構築者は、この人気のあるイメージの 非推奨を発表しました 。 現在、Dockerは利用者に、OpenJDKのビルドを商用のJavaベンダーから、あるいはAdoptiumプロジェクトから直接入手するよう求めています。既存のOpenJDKイメージの更新は今後行われないので、ユーザーは別のプロバイダに移行しない限り、Java SEを使用する際の機能およびセキュリティの更新が遅れるリスクが生じます。私は、Adoptiumプロジェクトによって維持されている 公式のEclipse Temurinイメージ が、代わりのイメージとして当然の選択であると信じています。 Eclipse Adoptium builds JDKs OpenJDK は、アップデートプロジェクトから直接バイナリアップデートを提供することはありません。2022年7月以降、これらの長期サポートされるJavaアップデートプロジェクトは、実行可能なOpenJDKバイナリを構築し配布するために Eclipse Adoptium に左右されます。 Adoptium は、最新のすぐに使える OpenJDK バイナリを構築、テストし、オープンソースライセンスの下で配布することを目的としたプロジェクトです。Adoptiumは、彼らのOpenJDKのビルドをTemurinと呼んでいます。これらは、幅広いプロセッサとオペレーティングシステムで利用できます。このTemurinバイナリは、5億回以上ダウンロードされ、世界中の企業の本番環境から信頼を得ています。Adoptiumは、Eclipseソフトウェア財団を拠点とするベンダーに依存しない ワーキンググループ が主導しています。 Adoptium コミュニティは、OpenJDK のソースコードから直接ビルドされたバイナリを提供しています。これらのTemurinバイナリは、直接ダウンロード、インストーラ、またはコンテナイメージとして利用でき、一定の条件の下で構築されたOpenJDKアップデートソースを忠実に表しています。 公式のDocker Hub Temurinイメージ には、いくつかのJava SEバージョンに対するOpenJDKアップデートの最新リリースが含まれており、様々なアプリケーションで徹底的にテストされています。このイメージは、OpenJDKイメージの代替としてそのまま使用できます。いくつかのOpenJDKイメージは、すでにTemurinバイナリを含んでいます。 OpenJDKのイメージからEclipse Temurinのイメージに移行する方法 Docker Hubの非推奨の決定には問題がありますが、解決策はあります。 OpenJDKのイメージ から、 Docker HubのEclipse Temurinの公式イメージ に移行することをお勧めします。 やり方は簡単です。以下のようなDockerfilesのFROM行を見るだけでいいのです。 FROM: openjdk:17 その行を以下のように変更します。 FROM eclipse-temurin:17 バージョン17以外のイメージの使用を変更するための手順も同じです。Adoptium コミュニティに問題を 報告 できます。 Red Hat サポート 皆さんもぜひ、Eclipse Temurinに乗り換えてみませんか?多くの アプリケーションイメージ や ベストプラクティスの事例 で、この変更は成功しています。 Red Hatは最近、Red Hat Runtimes、Red Hat OpenShift、およびRed Hat Build of OpenJDKの一部として、開発および本番における Temurinの直接サポートを発表しました 。Red Hatのサポートにより、お客様はTemurinへの移行をスムーズに行え、最新のビジネスアプリケーションとプロセスを統合し自動化する製品の構築に引き続き集中できるようになります。
Red Hatの織です。「 RHELの『よくわからないサービス』を調べる 」にインスパイアされて、OpenShift版を書いてみました。 OpenShiftをインストールすると、構築直後からたくさんのPodが動いています。 手元の構築直後のOpenShiftクラスター(AWS上のv4.11)を見てみると、Namespaceが67個あり、その中でPodが200個動いていました (ノード数によって多少変わります、DaemonSet等がいるので)。 $ oc get ns --no-headers | wc -l 67 $ oc get pod -A --no-headers | grep Running | wc -l 200 名前を見ただけで何をするPodかだいたいわかるものもあれば、何をしているのかよくわからないPodもいます。本記事では、こういった、OpenShiftにおける『よくわからないPod (もしくはコンテナ)』を調べる方法をまとめました。 公式ドキュメントで探す Podや、関連するワークロードリソース (Deployment、DaemonSet等) の名前でドキュメントを検索すると、情報が載っている場合があります。 Red Hat製品としてのOpenShiftのドキュメントは access.redhat.com もしくは docs.openshift.com にあります。 前者のURLには、日本語版もありますのでご活用ください。 後者のURLは英語版のみですが、複数のドキュメントにわたって串刺し検索ができます。 英語版のドキュメントは、ソースをGitHubで公開しています。手元でgrepしたい人は、git cloneしておくとよいかもしれません。 github.com 検索する際は、適切なブランチをチェックアウトするようにしてください (例えばv4.11の場合は enterprise-4.11 というブランチになります)。 Enhancementsドキュメントがあるか探す OpenShiftの機能拡張の多くは、 https://github.com/openshift/enhancements にドキュメントがあります。 KEPと同じ形式でデザインドキュメントが書かれており、Motivation, Goals, Non-Goals等がまとまっています。 「Podが何をしているか」という観点だと、記載の粒度が細かすぎるかもしれませんが、参考になる場合も多いです。 例えば openshift-ingress-canary というnamespaceで稼動する ingress-canary というPodについて、Enhancementsに情報がないか調べてみましょう。 $ grep -l -ri 'ingress[- ]canary' enhancements/ enhancements/ingress/ingress-fault-detection.md enhancements/network/dpu/smart-nic-ovn-offload.md 検索結果から、 https://github.com/openshift/enhancements/blob/master/enhancements/ingress/ingress-fault-detection.md を見てみると、参考になる情報が載っていることがわかります。 具体的には、ingress-operatorからcanary Podに対して定期的にRouter経由でアクセスすることで、Routeの稼動確認を行っていることがわかりました。 コンテナで稼動するコマンドのヘルプを見る ソースコードを見るのはちょっと...というときは、コンテナ内で動くプロセスを見て、そのコマンドに --help をつけて実行してみると、何をするPodかがわかる場合があります。 例えば、 openshift-network-diagnostics というnamespaceには network-check-source というPodがひとつと network-check-target というPodがノード数分動いています。 $ oc -n openshift-network-diagnostics get pod NAME READY STATUS RESTARTS AGE network-check-source-57b4967df6-w6gft 1/1 Running 0 17d network-check-target-7flvl 1/1 Running 0 17d network-check-target-8sh2v 1/1 Running 0 17d network-check-target-kgjzl 1/1 Running 0 17d network-check-target-psjdg 1/1 Running 0 17d network-check-target-sclmm 1/1 Running 0 17d network-check-target-zxrgm 1/1 Running 0 17d network-check-source に入って、動いているコマンドに --help をつけて実行してみます。 $ oc -n openshift-network-diagnostics exec network-check-source-57b4967df6-w6gft -- ps ax PID TTY STAT TIME COMMAND 1 ? Ssl 65:29 cluster-network-check-endpoints --listen 0.0.0.0:17698 --namespace openshift-network-diagnostics 59 ? Rs 0:00 ps ax $ oc -n openshift-network-diagnostics exec network-check-source-57b4967df6-w6gft -- cluster-network-check-endpoints --help Checks that a tcp connection can be opened to one or more endpoints. Usage: check-endpoints [flags] Flags: --config string Location of the master configuration file to run from. -h, --help help for check-endpoints --kubeconfig string Location of the master configuration file to run from. --listen string The ip:port to serve on. --namespace string Namespace where the controller is running. Auto-detected if run in cluster. --terminate-on-files stringArray A list of files. If one of them changes, the process will terminate. ヘルプメッセージから、複数のエンドポイントに対してTCPのコネクションが張れるかをチェックするPodだとわかります。 ソースコードのREADMEを見る GitHubのおかげで、ソースコードリポジトリのトップディレクトリに置かれたREADME.mdには、そのリポジトリに関する情報がまとまっていることが多いです。 問題は、「対象となるPodのソースコードがどこにあるか」をどうやって知るかですが、実はOpenShiftのPodは、ほとんどの場合 SOURCE_GIT_URL という環境変数にソースコードのGitリポジトリのURLが設定されています。 また追加の豆知識として、コンテナイメージの /root/buildinfo には、そのコンテナイメージのビルドに使用したDockerfileが置かれています。 先ほどの環境変数 SOURCE_GIT_URL もDockerfileの中で設定されています。コンテナのエントリーポイントの周辺情報も含めて調査する場合は、Dockerfileを見るのもよいでしょう。 Cluster Version Operatorの場合 例として、 openshift-cluster-version Namespaceの cluster-version-operator PodのGitリポジトリを調べてみましょう。まず、該当NamespaceのPodを確認します。 $ oc -n openshift-cluster-version get pod NAME READY STATUS RESTARTS AGE cluster-version-operator-678f49cffd-4rxpl 1/1 Running 0 17d 次に、該当Operatorの /root/buildinfo を見ます。Pod内に複数のコンテナが動いている場合は、目的のコンテナ名を -c オプションで指定してください。 $ oc -n openshift-cluster-version exec cluster-version-operator-678f49cffd-4rxpl -- ls -F /root/buildinfo Dockerfile-openshift-base-rhel8-v4.11.0-202209130958.p0.gf020942.assembly.stream Dockerfile-openshift-ose-base-v4.11.0-202209130958.p0.gf1330f6.assembly.stream Dockerfile-openshift-ose-cluster-version-operator-v4.11.0-202209130958.p0.gbd8aa51.assembly.stream Dockerfile-ubi8-8.6-754 content_manifests/ いくつかのDockerfileがあることがわかります。通常コンテナイメージはレイヤー化されていますが、今回の調査目的からすると、一番上の層の該当アプリが入っていると思われるDockerfileを見たいです。ほとんどの場合、どれが一番上の層のDockerfileかは、ファイル名を見るとだいたいわかると思います。 今回の場合は、 Dockerfile-openshift-ose-cluster-version-operator-v4.11.0-202209130958.p0.gbd8aa51.assembly.stream が該当します。 $ oc -n openshift-cluster-version exec cluster-version-operator-678f49cffd-4rxpl -- cat /root/buildinfo/Dockerfile-openshift-ose-cluster-version-operator-v4.11.0-202209130958.p0.gbd8aa51.assembly.stream | grep SOURCE_GIT_URL | head -n 1 ENV __doozer=merge OS_GIT_COMMIT=bd8aa51 OS_GIT_VERSION=4.11.0-202209130958.p0.gbd8aa51.assembly.stream-bd8aa51 SOURCE_DATE_EPOCH=1661361071 SOURCE_GIT_COMMIT=bd8aa51be5d4e5cc92462cd066742291ac86786b SOURCE_GIT_TAG=v1.0.0-850-gbd8aa51b SOURCE_GIT_URL=https://github.com/openshift/cluster-version-operator 環境変数からも確認してみます。 $ oc -n openshift-cluster-version exec cluster-version-operator-678f49cffd-4rxpl -- bash -c 'echo $SOURCE_GIT_URL' https://github.com/openshift/cluster-version-operator cluster-version-operatorのGitリポジトリは https://github.com/openshift/cluster-version-operator であることがわかりました。 上記URLを開くと、トップディレクトリのREADME.mdには残念ながらあまり情報は載っていませんでしたが、トップディレクトリ直下に docs というディレクトリがあり、ここにも情報がありそうです。ちょっとたどると結局 Enhancementsに飛ばされて 、 この辺り を読むとcluster-version-operatorの動きがわかってきます。 Node Tuning Operatorの場合 もうひとつの例として、 openshift-cluster-node-tuning-operator Namespaceの cluster-node-tuning-operator Podを調べてみます。まず、Dockerfileを確認します。 $ oc -n openshift-cluster-node-tuning-operator exec cluster-node-tuning-operator-7cb5cd8666-9755h -- ls /root/buildinfo ls: cannot access '/root/buildinfo': Permission denied command terminated with exit code 2 Permission denied で怒られてしまいました。実はこのOperatorは、Security Contextとして runAsNonRoot: true が設定されており、root以外のユーザーで起動するため、 /root/buildinfo を見ることができません。 $ oc -n openshift-cluster-node-tuning-operator get pod cluster-node-tuning-operator-7cb5cd8666-9755h -o json | jq .spec.securityContext { "runAsNonRoot": true, "runAsUser": 499, "seLinuxOptions": { "level": "s0:c14,c4" } } このような場合は、 oc debug pod --as-root=1 コマンドを使って、rootユーザーでコンテナに入ります。 $ oc -n openshift-cluster-node-tuning-operator debug pod/cluster-node-tuning-operator-7cb5cd8666-9755h --as-root=1 -- ls -F /root/buildinfo 2> /dev/null Dockerfile-openshift-base-rhel8-v4.11.0-202209130958.p0.gf020942.assembly.stream Dockerfile-openshift-ose-base-v4.11.0-202209130958.p0.gf1330f6.assembly.stream Dockerfile-openshift-ose-cluster-node-tuning-operator-v4.11.0-202209131448.p0.g8b73bd5.assembly.stream Dockerfile-ubi8-8.6-754 content_manifests/ Dockerfileが確認できたので、中を見てみましょう。 $ oc -n openshift-cluster-node-tuning-operator debug pod/cluster-node-tuning-operator-7cb5cd8666-9755h --as-root=1 -- cat /root/buildinfo/Dockerfile-openshift-ose-cluster-node-tuning-operator-v4.11.0-202209131448.p0.g8b73bd5.assembly.stream 2> /dev/null | grep SOURCE_GIT_URL | head -n 1 ENV __doozer=merge OS_GIT_COMMIT=8b73bd5 OS_GIT_VERSION=4.11.0-202209131448.p0.g8b73bd5.assembly.stream-8b73bd5 SOURCE_DATE_EPOCH=1663062448 SOURCE_GIT_COMMIT=8b73bd580a780daa44fabc1986e9ff4ed0bce33f SOURCE_GIT_TAG=8b73bd58 SOURCE_GIT_URL=https://github.com/openshift/cluster-node-tuning-operator ソースコードのリポジトリが https://github.com/openshift/cluster-node-tuning-operator であることがわかりました。今回はトップディレクトリのREADME.mdに有用な情報が載っており、cluster-node-tuning-operatorはカスタムリソースTunedを経由して、各ノードのtunedの設定を宣言的に管理するためのOperatorであることがわかりました。 最後に v4.11のPodについて、 スプレッドシート に情報をまとめてみました。
Red Hat で Java Platform Advocate として OpenJDK を担当している伊藤ちひろ( @chiroito )です。 この記事は、Red Hat Developerのブログ記事、 Red Hat expands support for Java with Eclipse Temurin | Red Hat Developer の翻訳記事です。 Eclipse Temurinは、Eclipse Adoptiumコミュニティが提供するOpenJDKの比較的新しいディストリビューションです。2021年8月の最初のJava SEリリース以来、多くのJava開発者が選択するディストリビューションとなっています。5億回以上ダウンロードされ、製品版OpenJDKの最も人気のあるビルドです。 私たちは、Eclipse Temurin のサポートを含めることで、Java アプリケーションのサポートを拡大しています。Temurin プロジェクトと、Red Hat がどのように Temurin の開発および本番使用のケースをサポートしているかについては、こちらの記事をお読みください。 Eclipse Temurinとは? Eclipse Temurinプロジェクトは、AdoptiumのOpenJDK(旧AdoptOpenJDK)のディストリビューションである。Temurinプロジェクトは、高品質のランタイムバイナリおよび関連技術の構築をサポートするコードとプロセスを提供し、Javaエコシステム全体で一般的に使用できるようにします。Temurinのバイナリは、クロスプラットフォームで、オープンソースライセンスであり、Java SE用のTCK認定を受けています。 Red Hat's support for Java Red Hatは、OpenJDKや、Red Hat JBoss Enterprise Application Platform、Quarkus、Hibernate、Keycloakなどのオープンソースプロジェクト、およびJavaエコシステムの分散クラウドアーキテクチャと関連テクノロジーに関連するプロジェクトに長年貢献しています。Red Hatは、Red Hat Runtimes、Red Hat OpenShift、およびOpenJDKのRed Hatビルドを介して直接、開発および本番でOpenJDKをサポートしています。 開発者は、Eclipse Temurinを使用できるようになりました。これは、OpenJDKのRed Hatビルドの一部としてもサポートされています。Red Hatは、OpenJDKのRed Hatビルドに付属する定評あるサポートと同様に、開発および本番使用の場合もサポートするつもりです。 プラットフォームを超えてEclipse Temurinを使用 開発者はJavaを使用して、業界を問わず、最新のビジネスに不可欠なアプリケーションを作成しています。これらのアプリケーションは、デスクトップからデータセンター、クラウドまで多岐にわたり、複数のオペレーティングシステムやプラットフォーム上に導入されています。Eclipse Temurinのサポートによって、複数のLinux OSやクラウド環境にもJavaアプリケーションを導入できるようになりました。 OpenJDK の配布とサポートの提供に加え、Red Hat のオープンソース製品ポートフォリオは、Red Hat Enterprise Linux と Microsoft Windows 上での Java をサポートしています。これらの製品を使用して、最新のビジネスアプリケーションとプロセスを作成、統合、自動化し、オペレーティングシステムからアプリケーションサービスまでのフルスタックサポートを単一ベンダーで実現できます。 Java アプリケーションを開発する場合、お好きなオペレーティング・システム用の RPM またはアーカイブをダウンロードできます。Red Hat は、物理環境と仮想環境の両方で、Linux と Windows ディストリビューションの本番サポートと開発サポート、および macOS の開発サポートを提供しています。詳細については、スタートガイドをお読みください。 AQAvit: エンタープライズクラスの品質を確保 Eclipse AQAvitは、標準的なJavaランタイムに厳選された追加テストのセットを提供し、実稼働環境での使用に適した品質水準を満たすことを保証します。AQAvitによるEclipse Temurinバイナリの検証の一環として、100以上のテストマシンとOSプラットフォーム上で350,000以上の自動テスト用にリリース成果物が作成されます。 AQAvitは、コンテナ化されたテストを含む、ビルドが最新の要件を満たしていることを保証します。テストは以下のように分類されます。 セキュリティ :既知のセキュリティ脆弱性をすべてテストする。 機能的な正しさ :OpenJDKの回帰テスト、機能テスト、および主要なアプリケーション/フレームワークテストを使用します。 パフォーマンス :公開されているメトリクスで最小限のスループットスコアを確保し、達成します。 スケーラビリティと耐久性 :負荷テストとストレステストの最小限の要件を満たしています。 AQAvitについてもっと知りたいですか? Shelley Lambert による、品質とそれが不可欠である理由を説明した素晴らしい講演をご覧ください。 AQA: You Are Soaking In It Java のサポートを受ける OpenJDK と Eclipse Temurin のサポートは、Red Hat Runtimes、Red Hat Enterprise Linux、および Red Hat OpenShift のサブスクリプションを通じて Red Hat のお客様に提供されます。詳細については、お近くの Red Hat 販売代理店または Red Hat 営業にお問い合わせください。お客様は、Red Hat Product Update and Support Lifecycle の下で、Java およびその他のランタイムのサポートを期待できます。 リソース 動画:Eclipse Temurinとは? Eclipse Temurinを使い始める Red Hat が Eclipse Adoptium ワーキンググループに参加 Eclipse Adoptium、初のJava SEリリースを達成 Red Hat、Microsoft WindowsにおけるOpenJDKの商用サポートを開始 OpenJDKの歴史と未来
レッドハットの杉村です。Ansible のテクニカルサポートをしています。 今回は以前のお問い合わせいただいた事例から、SSH (Secure Shell Protocol) について一つ紹介しようと思います。Ansible は Linux サーバを制御対象とするときは SSH で接続して処理を実行しますので、SSH の通信についてのトラブルは問題に直結します。 RHEL 8.6 + Ansible Core 2.13 で確認しています。 Ansible の基本的な動作原理 まずは Ansible はどうやって動いているのかというのを軽く振り返ってみます。 Ansible が動作するサーバをコントロールノード、制御対象をマネージドノードと呼びます。流れを大まかに説明しますと、この図のようになります。 ① YAMLで書かれたプレイブックからタスクごとに小さなプログラムを生成する ② ①で生成した小さなプログラムをマネージドノードに送り込む ③ マネージドノードで適切なインタプリタを検出して、それを使って処理を実行する ④ 処理結果を JSON 形式で返す このマネージドノードはコントロールノード自身になることもあります。たとえばクラウドやネットワーク機器を制御したい場合には、③の処理で REST API を呼び出したり、機器に対して対話的な処理を行ったりします。 マネージドノードへの接続方式や実行環境については、標準設定ではこちらの表に従います。今回の記事では、Linux サーバに対して SSH で接続するときのお話をします。 マネージドノード 接続方式 検出する実行環境 コントロールノード自身 local Python Linuxサーバ SSH Python Windowsサーバ WinRM PowerShell Ansible での主なトラブル 上記の図で説明しました①〜④がきちんと動作しないと Ansible は自動化の処理を実行できないため、プレイブックが動かないというときにはそれぞれに確認するポイントがあります。 問い合わせを受けたときは例えばこのようなものを確認します。問題によってはこの他にも確認するものはあります。 ① コントロールノードの問題 Ansible がインストールされていない YAML の文法が間違っている コレクションが参照できない 変数や認証情報が参照できない Python の実行環境が正しく設定されていない 暗号化データを取り扱うことや実行可能なスクリプトを生成することがウイルススキャンサービスによって阻害されている ② SSH でのマネージドノードへの接続の問題 ファイアウォールで通信が許可されていない (通常は 22/tcp) 名前解決ができない マネージドノードへのルーティングがない マネージドノードで SSH のサービスが起動していない コントロールノードから直接接続できないときに踏み台等を経由する設定が正しくない 認証ができない ユーザ名とパスワードの組み合わせが正しくない パスワードの入力待ちになっている 公開鍵と秘密鍵の組み合わせが正しくない StrictHostKeyChecking が有効になっていてホスト鍵の確認を求められる 暗号化スイートが双方で適合するものがない (強いのを要求されるのに弱いのしかないなど) 他の認証の仕組みとの連携に問題がある (Kerberos や SSO (Single Sign On) など) 接続ユーザにホームディレクトリがなく、ファイルが書き込めない 22/tcp は許可されているものの、必要な通信を中継機器で遮断している ③ マネージドノードでの動作の問題 Python インタプリタがインストールされていない Python インタプリタを検出できない 検出された Python インタプリタに必要なライブラリがインストールされていない 接続したユーザにスクリプトを実行する権限がない 外から送り込まれた実行可能なスクリプトの実行がウイルススキャンサービスによって阻害されている 処理に時間がかかりすぎて接続がタイムアウトする 大量にデータを生成してディスクがあふれる ④ マネージドノードから結果を受け取るときの問題 処理結果が膨大すぎて接続がタイムアウトする 大量にデータを受け取ってコントロールノード側のディスクがあふれる (fact を使うときなど) 処理結果が文字化けしている Ansible のデバッグレベル ansible-playbook コマンドでは、-v (--verbose) でデバッグレベルを指定します。現在のバージョン (Ansible Core 2.13) では、-vvvvvv (v が6つ) まで指定できます。多いほどより詳細なログが出力されます。主に使うのは -vvvv (v が4つ) です。Windows のときは5つ指定した情報を取得します。 docs.ansible.com vの数 意味合い (指定しない) 通常の出力 v verbose vv more verbose vvv debug vvvv connection debug vvvvv WinRM debug vvvvvv (下記リンク先のコードでしか使われてなさそうです) github.com その他、ANSIBLE_DEBUG 変数を true にすることで、Python の処理に深く切り込んでいくことができます。 実際の例 このような簡単なプレイブックを用意してみました。 --- - hosts: all gather_facts: false tasks: - name: ping ansible.builtin.ping: インベントリファイルも用意しておきます。 [RHEL8] 192.168.0.130 192.168.0.131 192.168.0.132 [RHEL8:vars] ansible_user=sugimura ansible_password=XXXXXXXX 実行します。 $ ansible-playbook -i inventory ping.yml PLAY [all] ********************************************************************************************************************************************************************************** TASK [ping] ********************************************************************************************************************************************************************************* ok: [192.168.0.131] ok: [192.168.0.132] ok: [192.168.0.130] PLAY RECAP ********************************************************************************************************************************************************************************** 192.168.0.130 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.0.131 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.0.132 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 これを -vvvv (vが4つ) で実行すると、このようにさまざまな情報が出力されます。 $ ansible-playbook -i inventory ping.yml -vvvv ansible-playbook [core 2.13.0] config file = /etc/ansible/ansible.cfg configured module search path = ['/home/sugimura/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules'] ansible python module location = /usr/lib/python3.9/site-packages/ansible ansible collection location = /home/sugimura/.ansible/collections:/usr/share/ansible/collections executable location = /usr/bin/ansible-playbook python version = 3.9.7 (default, Sep 13 2021, 08:18:39) [GCC 8.5.0 20210514 (Red Hat 8.5.0-3)] jinja version = 3.0.3 libyaml = True Using /etc/ansible/ansible.cfg as config file setting up inventory plugins host_list declined parsing /home/sugimura/inventory as it did not pass its verify_file() method script declined parsing /home/sugimura/inventory as it did not pass its verify_file() method auto declined parsing /home/sugimura/inventory as it did not pass its verify_file() method Parsed /home/sugimura/inventory inventory source with ini plugin Loading callback plugin default of type stdout, v2.0 from /usr/lib/python3.9/site-packages/ansible/plugins/callback/default.py Skipping callback 'default', as we already have a stdout callback. Skipping callback 'minimal', as we already have a stdout callback. Skipping callback 'oneline', as we already have a stdout callback. PLAYBOOK: ping.yml ************************************************************************************************************************************************************************** Positional arguments: ping.yml verbosity: 4 connection: smart timeout: 10 become_method: sudo tags: ('all',) inventory: ('/home/sugimura/inventory',) forks: 5 1 plays in ping.yml PLAY [all] ********************************************************************************************************************************************************************************** META: ran handlers TASK [ping] ********************************************************************************************************************************************************************************* task path: /home/sugimura/ping.yml:6 <192.168.0.130> ESTABLISH SSH CONNECTION FOR USER: sugimura <192.168.0.131> ESTABLISH SSH CONNECTION FOR USER: sugimura <192.168.0.130> SSH: EXEC sshpass -d10 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="sugimura"' -o ConnectTimeout=10 -o 'ControlPath="/home/sugimura/.ansible/cp/72daa41b21"' 192.168.0.130 '/bin/sh -c '"'"'echo ~sugimura && sleep 0'"'"'' <192.168.0.131> SSH: EXEC sshpass -d12 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="sugimura"' -o ConnectTimeout=10 -o 'ControlPath="/home/sugimura/.ansible/cp/1f1a06c0f9"' 192.168.0.131 '/bin/sh -c '"'"'echo ~sugimura && sleep 0'"'"'' <192.168.0.132> ESTABLISH SSH CONNECTION FOR USER: sugimura <192.168.0.132> SSH: EXEC sshpass -d14 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="sugimura"' -o ConnectTimeout=10 -o 'ControlPath="/home/sugimura/.ansible/cp/8577d474f2"' 192.168.0.132 '/bin/sh -c '"'"'echo ~sugimura && sleep 0'"'"'' ... さらに、 ANSIBLE_DEBUG=true もつけるとさらに詳しい情報が出てきます。実行に取り掛かる前にも設定ファイルの解釈やモジュールの読み込み、実行時刻など、さまざまな情報が取得できます。 $ ANSIBLE_DEBUG=true ansible-playbook -i inventory ping.yml -vvvv 1522811 1664675100.64203: starting run ansible-playbook [core 2.13.0] config file = /etc/ansible/ansible.cfg configured module search path = ['/home/sugimura/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules'] ansible python module location = /usr/lib/python3.9/site-packages/ansible ansible collection location = /home/sugimura/.ansible/collections:/usr/share/ansible/collections executable location = /usr/bin/ansible-playbook python version = 3.9.7 (default, Sep 13 2021, 08:18:39) [GCC 8.5.0 20210514 (Red Hat 8.5.0-3)] jinja version = 3.0.3 libyaml = True Using /etc/ansible/ansible.cfg as config file 1522811 1664675100.64446: Added group all to inventory 1522811 1664675100.64449: Added group ungrouped to inventory 1522811 1664675100.64453: Group all now contains ungrouped 1522811 1664675100.64456: Examining possible inventory source: /home/sugimura/inventory setting up inventory plugins 1522811 1664675100.64552: trying /usr/share/ansible/plugins/cache 1522811 1664675100.64557: trying /usr/lib/python3.9/site-packages/ansible/plugins/cache 1522811 1664675100.64591: Loading CacheModule 'memory' from /usr/lib/python3.9/site-packages/ansible/plugins/cache/memory.py (searched paths: /usr/lib/python3.9/site-packages/ansible/plugins/cache:/usr/share/ansible/plugins/cache) 1522811 1664675100.64606: trying /usr/share/ansible/plugins/inventory 1522811 1664675100.64609: trying /usr/lib/python3.9/site-packages/ansible/plugins/inventory 1522811 1664675100.64649: Loading InventoryModule 'host_list' from /usr/lib/python3.9/site-packages/ansible/plugins/inventory/host_list.py (searched paths: /usr/lib/python3.9/site-packages/ansible/plugins/inventory:/usr/share/ansible/plugins/inventory) 1522811 1664675100.64698: Loaded config def from plugin (inventory/script) 1522811 1664675100.64701: Loading InventoryModule 'script' from /usr/lib/python3.9/site-packages/ansible/plugins/inventory/script.py (searched paths: /usr/lib/python3.9/site-packages/ansible/plugins/inventory:/usr/share/ansible/plugins/inventory) 1522811 1664675100.64727: Loading InventoryModule 'auto' from /usr/lib/python3.9/site-packages/ansible/plugins/inventory/auto.py (searched paths: /usr/lib/python3.9/site-packages/ansible/plugins/inventory:/usr/share/ansible/plugins/inventory) 1522811 1664675100.64767: Loaded config def from plugin (inventory/yaml) ... PLAYBOOK: ping.yml ************************************************************************************************************************************************************************** Positional arguments: ping.yml verbosity: 4 connection: smart timeout: 10 become_method: sudo tags: ('all',) inventory: ('/home/sugimura/inventory',) forks: 5 1 plays in ping.yml ... 1522816 1664675100.74991: _low_level_execute_command(): starting 1522816 1664675100.74996: _low_level_execute_command(): executing: /bin/sh -c 'echo ~sugimura && sleep 0' <192.168.0.131> ESTABLISH SSH CONNECTION FOR USER: sugimura 1522818 1664675100.75114: running TaskExecutor() for 192.168.0.132/TASK: ping 1522818 1664675100.75153: in run() - task 00505699-f612-50dd-9ea3-00000000000a 1522818 1664675100.75163: variable 'ansible_search_path' from source: unknown 1522818 1664675100.75179: calling self._execute() 1522818 1664675100.75206: variable 'ansible_user' from source: group vars, precedence entry 'groups_inventory' 1522818 1664675100.75209: variable 'ansible_password' from source: group vars, precedence entry 'groups_inventory' 1522818 1664675100.75214: variable 'omit' from source: magic vars 1522818 1664675100.75264: variable 'omit' from source: magic vars <192.168.0.130> SSH: EXEC sshpass -d10 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="sugimura"' -o ConnectTimeout=10 -o 'ControlPath="/home/sugimura/.ansible/cp/72daa41b21"' 192.168.0.130 '/bin/sh -c '"'"'echo ~sugimura && sleep 0'"'"'' 1522818 1664675100.75279: variable 'omit' from source: magic vars 1522818 1664675100.75313: trying /usr/share/ansible/plugins/connection 1522818 1664675100.75324: trying /usr/lib/python3.9/site-packages/ansible/plugins/connection 1522818 1664675100.75355: Loading Connection 'ssh' from /usr/lib/python3.9/site-packages/ansible/plugins/connection/ssh.py (searched paths: /usr/share/ansible/plugins/connection:/usr/lib/python3.9/site-packages/ansible/plugins/connection) (found_in_cache=True, class_only=False) ... SSH の通信をデバッグする ようやく今回の本題です。このデバッグ情報を元にして、どのように通信を追いかけていくのかを紹介します。 ここではこのような問題を考えます。 RHEL のターミナルから ssh コマンドを使ってマネージドノードに接続して、コマンドの実行はできるものの、プレイブックを実行すると結果が返らずタイムアウトとなる。 コントロールノードとマネージドノードの間には VPN 機器がある。 通常のトラブルシューティングを行った結果、SSH の通信内容が怪しいと思われるところまでは絞り込んだ。(ここまでがまた大変ではありましたが) そこで、 ANSIBLE_DEBUG=true での詳細情報から、SSH での接続を読み解いていきます。上から順に追いかけます。 1. ssh で接続することを設定する 本当に ssh を使おうとしているのか、どのようなパラメータが設定されているかを確認します。いまの Ansible の実装では、接続部分はプラグインに切り離されています。コードを追いかけるときにはどのプラグインが使われているかを把握することが必要です。 1523091 1664675304.99557: trying /usr/share/ansible/plugins/connection 1523091 1664675304.99562: trying /usr/lib/python3.9/site-packages/ansible/plugins/connection 1523091 1664675304.99579: Loading Connection 'ssh' from /usr/lib/python3.9/site-packages/ansible/plugins/connection/ssh.py (searched paths: /usr/share/ansible/plugins/connection:/usr/lib/python3.9/site-packages/ansible/plugins/connection) (found_in_cache=True, class_only=False) 1523091 1664675304.99589: trying /usr/lib/python3.9/site-packages/ansible/plugins/shell 1523091 1664675304.99597: Loading ShellModule 'sh' from /usr/lib/python3.9/site-packages/ansible/plugins/shell/sh.py (found_in_cache=True, class_only=False) 1523091 1664675304.99603: Loading ShellModule 'sh' from /usr/lib/python3.9/site-packages/ansible/plugins/shell/sh.py (found_in_cache=True, class_only=False) 1523091 1664675304.99619: variable 'inventory_hostname' from source: host vars for '192.168.0.132' 1523091 1664675304.99621: variable 'ansible_password' from source: group vars, precedence entry 'groups_inventory' 1523091 1664675304.99623: variable 'ansible_user' from source: group vars, precedence entry 'groups_inventory' 1523091 1664675304.99692: Set connection var ansible_module_compression to ZIP_DEFLATED 1523091 1664675304.99697: Set connection var ansible_timeout to 10 1523091 1664675304.99699: Set connection var ansible_shell_executable to /bin/sh 1523091 1664675304.99703: Set connection var ansible_pipelining to False 1523091 1664675304.99705: Set connection var ansible_shell_type to sh 1523091 1664675304.99707: Set connection var ansible_connection to ssh 1523091 1664675304.99709: Set connection var ansible_host to 192.168.0.132 ... 2. ssh コマンドで接続確認用のコマンドを発行する 実行しているコマンドがログに記録されています。 sshpass -d10 ssh -vvv ... のところです。ホームディレクトリがあることを確認しています。 1523091 1664675304.99812: _low_level_execute_command(): starting 1523091 1664675304.99819: _low_level_execute_command(): executing: /bin/sh -c 'echo ~sugimura && sleep 0' <192.168.0.132> ESTABLISH SSH CONNECTION FOR USER: sugimura <192.168.0.132> SSH: EXEC sshpass -d10 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="sugimura"' -o ConnectTimeout=10 -o 'ControlPath="/home/sugimura/.ansible/cp/8577d474f2"' 192.168.0.132 '/bin/sh -c '"'"'echo ~sugimura && sleep 0'"'"'' さらに、ssh コマンドのデバッグログ ssh -vvv が出力されてきます。パケットの気持ちになって、それぞれ処理を読み解いていきます。 設定ファイル /home/sugimura/.ssh/config, /etc/ssh/ssh_config, そこから include されているファイル 提案する鍵交換アルゴリズム (ここでは diffie-hellman-group-exchange-sha1) が RHEL の crypto policy に合致しているか 通信のための socket が確立されたか リクエストを送信できたか (<<<) レスポンスが得られたか (>>>) 1523091 1664675305.00773: stderr chunk (state=2): >>>OpenSSH_8.0p1, OpenSSL 1.1.1k FIPS 25 Mar 2021 debug1: Reading configuration data /home/sugimura/.ssh/config debug3: kex names ok: [diffie-hellman-group-exchange-sha1] debug1: Reading configuration data /etc/ssh/ssh_config debug3: /etc/ssh/ssh_config line 54: Including file /etc/ssh/ssh_config.d/05-redhat.conf depth 0 debug1: Reading configuration data /etc/ssh/ssh_config.d/05-redhat.conf <<< 1523091 1664675305.00791: stderr chunk (state=3): >>>debug2: checking match for 'final all' host 192.168.0.132 originally 192.168.0.132 debug3: /etc/ssh/ssh_config.d/05-redhat.conf line 3: not matched 'final' debug2: match not found debug3: /etc/ssh/ssh_config.d/05-redhat.conf line 5: Including file /etc/crypto-policies/back-ends/openssh.config depth 1 (parse only) debug1: Reading configuration data /etc/crypto-policies/back-ends/openssh.config debug3: gss kex names ok: [gss-curve25519-sha256-,gss-nistp256-sha256-,gss-group14-sha256-,gss-group16-sha512-,gss-gex-sha1-,gss-group14-sha1-] debug3: kex names ok: [curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1] debug1: configuration requests final Match pass debug2: resolve_canonicalize: hostname 192.168.0.132 is address debug1: re-parsing configuration debug1: Reading configuration data /home/sugimura/.ssh/config debug3: kex names ok: [diffie-hellman-group-exchange-sha1] debug1: Reading configuration data /etc/ssh/ssh_config debug3: /etc/ssh/ssh_config line 54: Including file /etc/ssh/ssh_config.d/05-redhat.conf depth 0 debug1: Reading configuration data /etc/ssh/ssh_config.d/05-redhat.conf debug2: checking match for 'final all' host 192.168.0.132 originally 192.168.0.132 debug3: /etc/ssh/ssh_config.d/05-redhat.conf line 3: matched 'final' debug2: match found debug3: /etc/ssh/ssh_config.d/05-redhat.conf line 5: Including file /etc/crypto-policies/back-ends/openssh.config depth 1 debug1: Reading configuration data /etc/crypto-policies/back-ends/openssh.config debug3: gss kex names ok: [gss-curve25519-sha256-,gss-nistp256-sha256-,gss-group14-sha256-,gss-group16-sha512-,gss-gex-sha1-,gss-group14-sha1-] debug3: kex names ok: [curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1] <<< 1523091 1664675305.00803: stderr chunk (state=3): >>>debug1: auto-mux: Trying existing master debug2: fd 4 setting O_NONBLOCK <<< 1523091 1664675305.00820: stderr chunk (state=3): >>>debug2: mux_client_hello_exchange: master version 4 debug3: mux_client_forwards: request forwardings: 0 local, 0 remote debug3: mux_client_request_session: entering debug3: mux_client_request_alive: entering debug3: mux_client_request_alive: done pid = 1523069 debug3: mux_client_request_session: session request sent <<< 1523091 1664675305.03130: stdout chunk (state=3): >>>/home/sugimura <<< 1523091 1664675305.03203: stderr chunk (state=3): >>>debug3: mux_client_read_packet: read header failed: Broken pipe debug2: Received exit status from master 0 <<< 1523091 1664675305.03270: stderr chunk (state=3): >>><<< 1523091 1664675305.03278: stdout chunk (state=3): >>><<< <192.168.0.132> (0, b'/home/sugimura\n', b"OpenSSH_8.0p1, OpenSSL 1.1.1k FIPS 25 Mar 2021\r\ndebug1: Reading configuration data /home/sugimura/.ssh/config\r\ndebug3: kex names ok: [diffie-hellman-group-exchange-sha1]\r\ndebug1: ... 3. Python のプログラムを送信する準備をする ホームディレクトリがあることが確認できたので、SSH の通信セッションを使って Python のプログラムを送り込むためのディレクトリを作成する処理を見ていきます。 ここでも同様に入出力を見ます。 リクエストを送信できたか (<<<) レスポンスが得られたか (>>>) 正常終了したか (rc=0) もし失敗すると、他の /tmp 等のディレクトリに作成できないかを試みたりすることがあります。パーミッションの問題があるときはシェルに設定されている umask の値や、/etc/login.defs などに定義されているシステムワイドでの umask を確認します。ファイルシステムの mount オプションを疑うこともあります。 1523091 1664675305.03317: _low_level_execute_command(): starting 1523091 1664675305.03323: _low_level_execute_command(): executing: /bin/sh -c '( umask 77 && mkdir -p "` echo /home/sugimura/.ansible/tmp `"&& mkdir "` echo /home/sugimura/.ansible/tmp/ansible-tmp-1664675305.0331209-1523091-142821774396486 `" && echo ansible-tmp-1664675305.0331209-1523091-142821774396486="` echo /home/sugimura/.ansible/tmp/ansible-tmp-1664675305.0331209-1523091-142821774396486 `" ) && sleep 0' <192.168.0.132> ESTABLISH SSH CONNECTION FOR USER: sugimura <192.168.0.132> SSH: EXEC sshpass -d10 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="sugimura"' -o ConnectTimeout=10 -o 'ControlPath="/home/sugimura/.ansible/cp/8577d474f2"' 192.168.0.132 '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo /home/sugimura/.ansible/tmp `"&& mkdir "` echo /home/sugimura/.ansible/tmp/ansible-tmp-1664675305.0331209-1523091-142821774396486 `" && echo ansible-tmp-1664675305.0331209-1523091-142821774396486="` echo /home/sugimura/.ansible/tmp/ansible-tmp-1664675305.0331209-1523091-142821774396486 `" ) && sleep 0'"'"'' 1523091 1664675305.03972: stderr chunk (state=2): >>>OpenSSH_8.0p1, OpenSSL 1.1.1k FIPS 25 Mar 2021 ... debug1: auto-mux: Trying existing master debug2: fd 4 setting O_NONBLOCK <<< 1523091 1664675305.28372: stderr chunk (state=3): >>>debug2: mux_client_hello_exchange: master version 4 debug3: mux_client_forwards: request forwardings: 0 local, 0 remote debug3: mux_client_request_session: entering debug3: mux_client_request_alive: entering <<< 1523091 1664675305.28381: stderr chunk (state=3): >>>debug3: mux_client_request_alive: done pid = 1523069 debug3: mux_client_request_session: session request sent <<< 1523091 1664675305.30525: stderr chunk (state=3): >>>debug3: mux_client_read_packet: read header failed: Broken pipe debug2: Received exit status from master 0 <<< 1523091 1664675305.30614: stderr chunk (state=3): >>><<< 1523091 1664675305.30624: stdout chunk (state=3): >>><<< <192.168.0.132> (0, b'', b"OpenSSH_8.0p1, OpenSSL 1.1.1k FIPS 25 Mar 2021\r\ndebug1: Reading configuration data /home/sugimura/.ssh/config\r\ndebug3: kex names ok: [diffie-hellman-group-exchange-sha1]\r\ndebug1: Reading ... 1523091 1664675305.06308: _low_level_execute_command() done: rc=0, stdout=ansible-tmp-1664675305.0331209-1523091-142821774396486=/home/sugimura/.ansible/tmp/ansible-tmp-1664675305.0331209-1523091-142821774396486 , stderr=OpenSSH_8.0p1, OpenSSL 1.1.1k FIPS 25 Mar 2021 ... 4. マネージドノードの Python インタプリタを検出する ping モジュールからは Python の小さなプログラムを生成したため、それをマネージドノードで実行するために利用できるものがあるかどうかを探します。 モジュールによっては debug モジュールのように Python インタプリタを必要としないものもあるため、モジュールを解釈しなければこの処理に入りません。 Python インタプリタの検出について、外側から見た大まかな概要についてはこちらの記事もご覧ください。 rheb.hatenablog.com 1523091 1664675305.06391: ANSIBALLZ: Using lock for ansible.builtin.ping 1523091 1664675305.06394: ANSIBALLZ: Acquiring lock 1523091 1664675305.06397: ANSIBALLZ: Lock acquired: 139933950381504 1523091 1664675305.06399: ANSIBALLZ: Creating module 1523091 1664675305.15378: ANSIBALLZ: Writing module into payload 1523091 1664675305.15434: ANSIBALLZ: Writing module 1523091 1664675305.15450: ANSIBALLZ: Renaming module 1523091 1664675305.15454: ANSIBALLZ: Done creating module 1523091 1664675305.15472: variable 'ansible_facts' from source: unknown 1523091 1664675305.15476: variable 'inventory_hostname' from source: host vars for '192.168.0.132' <192.168.0.132> Attempting python interpreter discovery 1523091 1664675305.15485: _low_level_execute_command(): starting 1523091 1664675305.15490: _low_level_execute_command(): executing: /bin/sh -c 'echo PLATFORM; uname; echo FOUND; command -v '"'"'python3.10'"'"'; command -v '"'"'python3.9'"'"'; command -v '"'"'python3.8'"'"'; command -v '"'"'python3.7'"'"'; command -v '"'"'python3.6'"'"'; command -v '"'"'python3.5'"'"'; command -v '"'"'/usr/bin/python3'"'"'; command -v '"'"'/usr/libexec/platform-python'"'"'; command -v '"'"'python2.7'"'"'; command -v '"'"'/usr/bin/python'"'"'; command -v '"'"'python'"'"'; echo ENDFOUND && sleep 0' <192.168.0.132> ESTABLISH SSH CONNECTION FOR USER: sugimura <192.168.0.132> SSH: EXEC sshpass -d10 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="sugimura"' -o ConnectTimeout=10 -o 'ControlPath="/home/sugimura/.ansible/cp/8577d474f2"' 192.168.0.132 '/bin/sh -c '"'"'echo PLATFORM; uname; echo FOUND; command -v '"'"'"'"'"'"'"'"'python3.10'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.9'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.8'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.7'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.6'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.5'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'/usr/bin/python3'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'/usr/libexec/platform-python'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python2.7'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'/usr/bin/python'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python'"'"'"'"'"'"'"'"'; echo ENDFOUND && sleep 0'"'"'' 1523091 1664675305.16121: stderr chunk (state=2): >>>OpenSSH_8.0p1, OpenSSL 1.1.1k FIPS 25 Mar 2021 ... <<< 1523091 1664675305.18538: stdout chunk (state=3): >>>PLATFORM <<< 1523091 1664675305.18587: stdout chunk (state=3): >>>Linux <<< 1523091 1664675305.18621: stdout chunk (state=3): >>>FOUND /usr/bin/python3.9 /usr/bin/python3 /usr/libexec/platform-python ENDFOUND <<< 1523091 1664675305.18779: stderr chunk (state=3): >>>debug3: mux_client_read_packet: read header failed: Broken pipe debug2: Received exit status from master 0 <<< 1523091 1664675305.18796: stderr chunk (state=3): >>><<< 1523091 1664675305.18801: stdout chunk (state=3): >>><<< ... 1523091 1664675305.18826: _low_level_execute_command() done: rc=0, stdout=PLATFORM Linux FOUND /usr/bin/python3.9 /usr/bin/python3 /usr/libexec/platform-python ENDFOUND , stderr=OpenSSH_8.0p1, OpenSSL 1.1.1k FIPS 25 Mar 2021 ... 1523091 1664675305.18836 [192.168.0.132]: found interpreters: ['/usr/bin/python3.9', '/usr/bin/python3', '/usr/libexec/platform-python'] 5. 検出した Python インタプリタを調べる Python インタプリタを実際に実行して、結果を得ることができるかを確認しています。1234 bytes の何か小さなデータを送っていることはわかりますが、その内容はログには出てきていませんでした。OS についての情報を収集して結果が JSON で返されていることがわかります。 ここでも入出力と rc=0 (正常終了) であることを確認します。 1523091 1664675305.18877: _low_level_execute_command(): starting 1523091 1664675305.18885: _low_level_execute_command(): executing: /bin/sh -c '/usr/bin/python3.9 && sleep 0' <192.168.0.132> ESTABLISH SSH CONNECTION FOR USER: sugimura <192.168.0.132> SSH: EXEC sshpass -d10 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="sugimura"' -o ConnectTimeout=10 -o 'ControlPath="/home/sugimura/.ansible/cp/8577d474f2"' 192.168.0.132 '/bin/sh -c '"'"'/usr/bin/python3.9 && sleep 0'"'"'' 1523091 1664675305.19179: Sending initial data 1523091 1664675305.19195: Sent initial data (1234 bytes) ... <<< 1523091 1664675305.19553: stderr chunk (state=3): >>>debug3: mux_client_request_alive: done pid = 1523069 debug3: mux_client_request_session: session request sent <<< 1523091 1664675305.23304: stdout chunk (state=3): >>>{"platform_dist_result": [], "osrelease_content": "NAME=\"Red Hat Enterprise Linux\"\nVERSION=\"8.6 (Ootpa)\"\nID=\"rhel\"\nID_LIKE=\"fedora\"\nVERSION_ID=\"8.6\"\nPLATFORM_ID=\"platform:el8\"\nPRETTY_NAME=\"Red Hat Enterprise Linux 8.6 (Ootpa)\"\nANSI_COLOR=\"0;31\"\nCPE_NAME=\"cpe:/o:redhat:enterprise_linux:8::baseos\"\nHOME_URL=\"https://www.redhat.com/\"\nDOCUMENTATION_URL=\"https://access.redhat.com/documentation/red_hat_enterprise_linux/8/\"\nBUG_REPORT_URL=\"https://bugzilla.redhat.com/\"\n\nREDHAT_BUGZILLA_PRODUCT=\"Red Hat Enterprise Linux 8\"\nREDHAT_BUGZILLA_PRODUCT_VERSION=8.6\nREDHAT_SUPPORT_PRODUCT=\"Red Hat Enterprise Linux\"\nREDHAT_SUPPORT_PRODUCT_VERSION=\"8.6\"\n"} <<< 1523091 1664675305.23927: stderr chunk (state=3): >>>debug3: mux_client_read_packet: read header failed: Broken pipe debug2: Received exit status from master 0 <<< 1523091 1664675305.23952: stderr chunk (state=3): >>><<< 1523091 1664675305.23955: stdout chunk (state=3): >>><<< ... 1523091 1664675305.23973: _low_level_execute_command() done: rc=0, stdout={"platform_dist_result": [], "osrelease_content": "NAME=\"Red Hat Enterprise Linux\"\nVERSION=\"8.6 (Ootpa)\"\nID=\"rhel\"\nID_LIKE=\"fedora\"\nVERSION_ID=\"8.6\"\nPLATFORM_ID=\"platform:el8\"\nPRETTY_NAME=\"Red Hat Enterprise Linux 8.6 (Ootpa)\"\nANSI_COLOR=\"0;31\"\nCPE_NAME=\"cpe:/o:redhat:enterprise_linux:8::baseos\"\nHOME_URL=\"https://www.redhat.com/\"\nDOCUMENTATION_URL=\"https://access.redhat.com/documentation/red_hat_enterprise_linux/8/\"\nBUG_REPORT_URL=\"https://bugzilla.redhat.com/\"\n\nREDHAT_BUGZILLA_PRODUCT=\"Red Hat Enterprise Linux 8\"\nREDHAT_BUGZILLA_PRODUCT_VERSION=8.6\nREDHAT_SUPPORT_PRODUCT=\"Red Hat Enterprise Linux\"\nREDHAT_SUPPORT_PRODUCT_VERSION=\"8.6\"\n"} , stderr=OpenSSH_8.0p1, OpenSSL 1.1.1k FIPS 25 Mar 2021 6. ping モジュールから生成された Python のプログラムを送信する ここまで来てやっと、ping モジュールから生成した Python のプログラムを送信します。ping.py を元にして AnsiballZ_ping.py を生成し、sftp で送信しています。 ファイルを送信して相手先に書き込むというのはこれまでのコマンドの実行と比べるとそれなりに複雑なため、注意深く見ていく必要があります。 1523091 1664675305.24066: variable 'ansible_facts' from source: unknown 1523091 1664675305.24071: variable 'ansible_facts' from source: unknown 1523091 1664675305.24098: ANSIBALLZ: using cached module: /home/sugimura/.ansible/tmp/ansible-local-1523087yurgsn13/ansiballz_cache/ansible.modules.ping-ZIP_DEFLATED 1523091 1664675305.24123: variable 'ansible_facts' from source: unknown Using module file /usr/lib/python3.9/site-packages/ansible/modules/ping.py 1523091 1664675305.24179: transferring module to remote /home/sugimura/.ansible/tmp/ansible-tmp-1664675305.0331209-1523091-142821774396486/AnsiballZ_ping.py <192.168.0.132> PUT /home/sugimura/.ansible/tmp/ansible-local-1523087yurgsn13/tmpl67x1t1y TO /home/sugimura/.ansible/tmp/ansible-tmp-1664675305.0331209-1523091-142821774396486/AnsiballZ_ping.py <192.168.0.132> SSH: EXEC sshpass -d10 sftp -o BatchMode=no -b - -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="sugimura"' -o ConnectTimeout=10 -o 'ControlPath="/home/sugimura/.ansible/cp/8577d474f2"' '[192.168.0.132]' 1523091 1664675305.24611: Sending initial data 1523091 1664675305.24627: Sent initial data (175 bytes) 1523091 1664675305.25089: stderr chunk (state=3): >>>OpenSSH_8.0p1, OpenSSL 1.1.1k FIPS 25 Mar 2021 ... debug3: kex names ok: [curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1] <<< 1523091 1664675305.25115: stderr chunk (state=3): >>>debug1: auto-mux: Trying existing master debug2: fd 4 setting O_NONBLOCK <<< 1523091 1664675305.25134: stderr chunk (state=3): >>>debug2: mux_client_hello_exchange: master version 4 debug3: mux_client_forwards: request forwardings: 0 local, 0 remote debug3: mux_client_request_session: entering debug3: mux_client_request_alive: entering debug3: mux_client_request_alive: done pid = 1523069 <<< 1523091 1664675305.25140: stderr chunk (state=3): >>>debug3: mux_client_request_session: session request sent <<< 1523091 1664675305.26885: stderr chunk (state=3): >>>debug2: Remote version: 3 debug2: Server supports extension "posix-rename@openssh.com" revision 1 debug2: Server supports extension "statvfs@openssh.com" revision 2 debug2: Server supports extension "fstatvfs@openssh.com" revision 2 debug2: Server supports extension "hardlink@openssh.com" revision 1 debug2: Server supports extension "fsync@openssh.com" revision 1 debug2: Server supports extension "lsetstat@openssh.com" revision 1 debug3: Sent message fd 3 T:16 I:1 <<< 1523091 1664675305.26918: stderr chunk (state=3): >>>debug3: SSH_FXP_REALPATH . -> /home/sugimura size 0 debug3: Looking up /home/sugimura/.ansible/tmp/ansible-local-1523087yurgsn13/tmpl67x1t1y debug3: Sent message fd 3 T:17 I:2 <<< 1523091 1664675305.26927: stdout chunk (state=3): >>>sftp> put /home/sugimura/.ansible/tmp/ansible-local-1523087yurgsn13/tmpl67x1t1y /home/sugimura/.ansible/tmp/ansible-tmp-1664675305.0331209-1523091-142821774396486/AnsiballZ_ping.py <<< 1523091 1664675305.26938: stderr chunk (state=3): >>>debug3: Received stat reply T:101 I:2 debug1: Couldn't stat remote file: No such file or directory debug3: Sent message SSH2_FXP_OPEN I:3 P:/home/sugimura/.ansible/tmp/ansible-tmp-1664675305.0331209-1523091-142821774396486/AnsiballZ_ping.py <<< 1523091 1664675305.26968: stderr chunk (state=3): >>>debug3: Sent message SSH2_FXP_WRITE I:4 O:0 S:32768 <<< 1523091 1664675305.27142: stderr chunk (state=3): >>>debug3: SSH2_FXP_STATUS 0 debug3: In write loop, ack for 4 32768 bytes at 0 debug3: Sent message SSH2_FXP_WRITE I:5 O:32768 S:32768 debug3: Sent message SSH2_FXP_WRITE I:6 O:65536 S:32768 debug3: Sent message SSH2_FXP_WRITE I:7 O:98304 S:28929 <<< 1523091 1664675305.27425: stderr chunk (state=3): >>>debug3: SSH2_FXP_STATUS 0 debug3: In write loop, ack for 5 32768 bytes at 32768 <<< 1523091 1664675305.27454: stderr chunk (state=3): >>>debug3: SSH2_FXP_STATUS 0 debug3: In write loop, ack for 6 32768 bytes at 65536 <<< 1523091 1664675305.27470: stderr chunk (state=3): >>>debug3: SSH2_FXP_STATUS 0 debug3: In write loop, ack for 7 28929 bytes at 98304 debug3: Sent message SSH2_FXP_CLOSE I:4 <<< 1523091 1664675305.27488: stderr chunk (state=3): >>>debug3: SSH2_FXP_STATUS 0 <<< 1523091 1664675305.27539: stderr chunk (state=3): >>>debug3: mux_client_read_packet: read header failed: Broken pipe debug2: Received exit status from master 0 <<< 1523091 1664675305.27634: stderr chunk (state=3): >>><<< 1523091 1664675305.27645: stdout chunk (state=3): >>><<< ... 1523091 1664675305.27677: done transferring module to remote 7. ping モジュールから生成された Python のプログラムに実行権限を付与する 無事 AnsiballZ_ping.py を送りつけたら、実行権限を付与します。 chmod u+x コマンドを実行しています。 こういうコマンドでもマネージドノード上の設定によっては何らかの理由で失敗する場合があるので、注意深く見ていく必要があります。chmod の失敗はファイルシステム (LVM や NFS など) の設定が疑わしいことがあります。 1523091 1664675305.27690: _low_level_execute_command(): starting 1523091 1664675305.27695: _low_level_execute_command(): executing: /bin/sh -c 'chmod u+x /home/sugimura/.ansible/tmp/ansible-tmp-1664675305.0331209-1523091-142821774396486/ /home/sugimura/.ansible/tmp/ansible-tmp-1664675305.0331209-1523091-142821774396486/AnsiballZ_ping.py && sleep 0' <192.168.0.132> ESTABLISH SSH CONNECTION FOR USER: sugimura <192.168.0.132> SSH: EXEC sshpass -d10 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="sugimura"' -o ConnectTimeout=10 -o 'ControlPath="/home/sugimura/.ansible/cp/8577d474f2"' 192.168.0.132 '/bin/sh -c '"'"'chmod u+x /home/sugimura/.ansible/tmp/ansible-tmp-1664675305.0331209-1523091-142821774396486/ /home/sugimura/.ansible/tmp/ansible-tmp-1664675305.0331209-1523091-142821774396486/AnsiballZ_ping.py && sleep 0'"'"'' 1523091 1664675305.28351: stderr chunk (state=2): ... 1523091 1664675305.30648: _low_level_execute_command() done: rc=0, stdout=, stderr=OpenSSH_8.0p1, OpenSSL 1.1.1k FIPS 25 Mar 2021 8. ping モジュールから生成された Python のプログラムを実行する ようやくモジュールを実行します。4. では ['/usr/bin/python3.9', '/usr/bin/python3', '/usr/libexec/platform-python'] の Python インタプリタを検出して 5. では最初の /usr/bin/python3.9 で実行していましたが、5. の処理結果からはマネージドノードの OS は RHEL 8 であることが判断できたので、INTERPRETER_PYTHON_DISTRO_MAP の定義から RHEL 8 を探して /usr/libexec/platform-python を使って実行します。この定義は ansible-config dump で確認できます。 $ ansible-config dump | grep INTERPRETER INTERPRETER_PYTHON(default) = auto INTERPRETER_PYTHON_DISTRO_MAP(default) = {'redhat': {'6': '/usr/bin/python', '8': '/usr/libexec/platform-python', '9': '/usr/bin/python3'}, 'debian': {'8': '/usr/bin/python', '10': '/usr/bin/python3'}, 'fedora': {'23': '/usr/bin/python3'}, 'ubuntu': {'14': '/usr/bin/python', '16': '/usr/bin/python3'}} INTERPRETER_PYTHON_FALLBACK(default) = ['python3.10', 'python3.9', 'python3.8', 'python3.7', 'python3.6', 'python3.5', '/usr/bin/python3', '/usr/libexec/platform-python', 'python2.7', '/usr/bin/python', 'python'] このように /usr/libexec/platform-python で AnsiballZ_ping.py を実行しています。>>> のレスポンスを見ると ”pong” が返されていることがわかります。Ansible のモジュールは JSON 形式で結果を返す約束になっています。 正常終了 (rc=0) も見ておきます。 1523091 1664675305.30654: _low_level_execute_command(): starting 1523091 1664675305.30659: _low_level_execute_command(): executing: /bin/sh -c '/usr/libexec/platform-python /home/sugimura/.ansible/tmp/ansible-tmp-1664675305.0331209-1523091-142821774396486/AnsiballZ_ping.py && sleep 0' <192.168.0.132> ESTABLISH SSH CONNECTION FOR USER: sugimura <192.168.0.132> SSH: EXEC sshpass -d10 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="sugimura"' -o ConnectTimeout=10 -o 'ControlPath="/home/sugimura/.ansible/cp/8577d474f2"' -tt 192.168.0.132 '/bin/sh -c '"'"'/usr/libexec/platform-python /home/sugimura/.ansible/tmp/ansible-tmp-1664675305.0331209-1523091-142821774396486/AnsiballZ_ping.py && sleep 0'"'"'' 1523091 1664675305.31222: stderr chunk (state=2): >>>OpenSSH_8.0p1, OpenSSL 1.1.1k FIPS 25 Mar 2021 ... <<< 1523091 1664675305.31286: stderr chunk (state=3): >>>debug3: mux_client_request_alive: done pid = 1523069 debug3: mux_client_request_session: session request sent <<< 1523091 1664675305.43192: stdout chunk (state=3): >>> {"ping": "pong", "invocation": {"module_args": {"data": "pong"}}} <<< 1523091 1664675305.44110: stderr chunk (state=3): >>>debug3: mux_client_read_packet: read header failed: Broken pipe debug2: Received exit status from master 0 Shared connection to 192.168.0.132 closed. <<< 1523091 1664675305.44191: stderr chunk (state=3): >>><<< 1523091 1664675305.44199: stdout chunk (state=3): >>><<< ... 1523091 1664675305.44222: _low_level_execute_command() done: rc=0, stdout= {"ping": "pong", "invocation": {"module_args": {"data": "pong"}}} , stderr=OpenSSH_8.0p1, OpenSSL 1.1.1k FIPS 25 Mar 2021 ... debug2: fd 4 setting O_NONBLOCK debug2: mux_client_hello_exchange: master version 4 debug3: mux_client_forwards: request forwardings: 0 local, 0 remote debug3: mux_client_request_session: entering debug3: mux_client_request_alive: entering debug3: mux_client_request_alive: done pid = 1523069 debug3: mux_client_request_session: session request sent debug3: mux_client_read_packet: read header failed: Broken pipe debug2: Received exit status from master 0 Shared connection to 192.168.0.132 closed. 1523091 1664675305.44261: done with _execute_module (ansible.builtin.ping, {'_ansible_check_mode': False, '_ansible_no_log': False, '_ansible_debug': True, '_ansible_diff': False, '_ansible_verbosity': 4, '_ansible_version': '2.13.0', '_ansible_module_name': 'ansible.builtin.ping', '_ansible_syslog_facility': 'LOG_USER', '_ansible_selinux_special_fs': ['fuse', 'nfs', 'vboxsf', 'ramfs', '9p', 'vfat'], '_ansible_string_conversion_action': 'warn', '_ansible_socket': None, '_ansible_shell_executable': '/bin/sh', '_ansible_keep_remote_files': False, '_ansible_tmpdir': '/home/sugimura/.ansible/tmp/ansible-tmp-1664675305.0331209-1523091-142821774396486/', '_ansible_remote_tmp': '~/.ansible/tmp'}) もし ping モジュールではなく他のモジュールでライブラリが足りないためにエラーとなるというようなことがあれば、ここのあたりを詳しく見ていくと Python のエラーが見つかることがあります。 9. ping モジュールから生成された Python のプログラムを削除する モジュールの実行が終わったら、送りつけたプログラムをディレクトリごと削除します。3. で実行した mkdir コマンドに対応して、 rm -f -r コマンドを実行しています。 1523091 1664675305.44269: _low_level_execute_command(): starting 1523091 1664675305.44273: _low_level_execute_command(): executing: /bin/sh -c 'rm -f -r /home/sugimura/.ansible/tmp/ansible-tmp-1664675305.0331209-1523091-142821774396486/ > /dev/null 2>&1 && sleep 0' <192.168.0.132> ESTABLISH SSH CONNECTION FOR USER: sugimura <192.168.0.132> SSH: EXEC sshpass -d10 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o 'User="sugimura"' -o ConnectTimeout=10 -o 'ControlPath="/home/sugimura/.ansible/cp/8577d474f2"' 192.168.0.132 '/bin/sh -c '"'"'rm -f -r /home/sugimura/.ansible/tmp/ansible-tmp-1664675305.0331209-1523091-142821774396486/ > /dev/null 2>&1 && sleep 0'"'"'' 1523091 1664675305.44857: stderr chunk (state=2): ... 1523091 1664675305.47347: _low_level_execute_command() done: rc=0, stdout=, stderr=OpenSSH_8.0p1, OpenSSL 1.1.1k FIPS 25 Mar 2021 10. モジュール実行の後処理 タスクの実行が終わったら、返されたデータの整形や後処理をします。 1523091 1664675305.47359: handler run complete 1523091 1664675305.47371: variable 'ansible_facts' from source: unknown 1523091 1664675305.47436: variable 'ansible_facts' from source: unknown 1523091 1664675305.47461: attempt loop complete, returning result 1523091 1664675305.47465: _execute() done 1523091 1664675305.47467: dumping result to json 1523091 1664675305.47469: done dumping result, returning 1523091 1664675305.47477: done running TaskExecutor() for 192.168.0.132/TASK: ping [00505699-f612-c152-b54d-000000000008] 1523091 1664675305.47480: sending task result for task 00505699-f612-c152-b54d-000000000008 1523091 1664675305.47527: done sending task result for task 00505699-f612-c152-b54d-000000000008 1523091 1664675305.47531: WORKER PROCESS EXITING ok: [192.168.0.132] => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": false, "invocation": { "module_args": { "data": "pong" } }, "ping": "pong" } 1523087 1664675305.47873: no more pending results, returning what we have 異常なとき 先ほど上で見たのは正常に動作している流れを追ってみたものです。 ANSIBLE_DEBUG=true で Ansible から見た処理としては意図通り流れている場合、より下のレイヤの SSH を追いかける必要があることがあります。 マネージドノードの /etc/ssh/sshd_config ファイルで LogLevel DEBUG3 として、sshd サービスを再起動します。 $ sudo su - # systemctl restart sshd そして再度 ansible-playbook -vvvv ... を実行して、マネージドノード側の /var/log/secure を見ます。例えば Python インタプリタを検出するあたりでは、このようなログが出ていました。 Oct 2 11:51:00 rhel86 sshd[24024]: debug3: receive packet: type 90 Oct 2 11:51:00 rhel86 sshd[24024]: debug1: server_input_channel_open: ctype session rchan 2 win 2097152 max 32768 Oct 2 11:51:00 rhel86 sshd[24024]: debug1: input_session_request Oct 2 11:51:00 rhel86 sshd[24024]: debug1: channel 0: new [server-session] Oct 2 11:51:00 rhel86 sshd[24024]: debug1: session_new: session 0 Oct 2 11:51:00 rhel86 sshd[24024]: debug1: session_open: channel 0 Oct 2 11:51:00 rhel86 sshd[24024]: debug1: session_open: session 0: link with channel 0 Oct 2 11:51:00 rhel86 sshd[24024]: debug1: server_input_channel_open: confirm session Oct 2 11:51:00 rhel86 sshd[24024]: debug3: send packet: type 91 Oct 2 11:51:00 rhel86 sshd[24024]: debug3: receive packet: type 98 Oct 2 11:51:00 rhel86 sshd[24024]: debug1: server_input_channel_req: channel 0 request env reply 0 Oct 2 11:51:00 rhel86 sshd[24024]: debug1: session_by_channel: session 0 channel 0 Oct 2 11:51:00 rhel86 sshd[24024]: debug1: session_input_channel_req: session 0 req env Oct 2 11:51:00 rhel86 sshd[24024]: debug2: Setting env 0: LANG=ja_JP.UTF-8 Oct 2 11:51:00 rhel86 sshd[24024]: debug3: receive packet: type 98 Oct 2 11:51:00 rhel86 sshd[24024]: debug1: server_input_channel_req: channel 0 request exec reply 1 Oct 2 11:51:00 rhel86 sshd[24024]: debug1: session_by_channel: session 0 channel 0 Oct 2 11:51:00 rhel86 sshd[24024]: debug1: session_input_channel_req: session 0 req exec Oct 2 11:51:00 rhel86 sshd[24024]: Starting session: command for sugimura from 192.168.0.130 port 35746 id 0 Oct 2 11:51:00 rhel86 sshd[24024]: debug3: mm_audit_run_command entering command /bin/sh -c 'echo PLATFORM; uname; echo FOUND; command -v '"'"'python3.10'"'"'; command -v '"'"'python3.9'"'"'; command -v '"'"'python3.8'"'"'; command -v '"'"'python3.7'"'"'; command -v '"'"'python3.6'"'"'; command -v '"'"'python3.5'"'"'; command -v '"'"'/usr/bin/python3'"'"'; command -v '"'"'/usr/libexec/platform-python'"'"'; command -v '"'"'python2.7'"'"'; command -v '"'"'/usr/bin/python'"'"'; command -v '"'"'python'"'"'; echo ENDFOUND && sleep 0' Oct 2 11:51:00 rhel86 sshd[24024]: debug3: mm_request_send entering: type 114 Oct 2 11:51:00 rhel86 sshd[24024]: debug3: mm_request_receive_expect entering: type 115 Oct 2 11:51:00 rhel86 sshd[24024]: debug3: mm_request_receive entering Oct 2 11:51:00 rhel86 sshd[24024]: debug2: fd 17 setting O_NONBLOCK Oct 2 11:51:00 rhel86 sshd[24024]: debug2: fd 16 setting O_NONBLOCK Oct 2 11:51:00 rhel86 sshd[24024]: debug2: fd 19 setting O_NONBLOCK Oct 2 11:51:00 rhel86 sshd[24024]: debug3: mm_forward_audit_messages: entering Oct 2 11:51:00 rhel86 sshd[24024]: debug3: send packet: type 99 Oct 2 11:51:00 rhel86 sshd[24024]: debug1: Received SIGCHLD. Oct 2 11:51:00 rhel86 sshd[24024]: debug1: session_by_pid: pid 24065 Oct 2 11:51:00 rhel86 sshd[24024]: debug1: session_exit_message: session 0 channel 0 pid 24065 Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: request exit-status confirm 0 Oct 2 11:51:00 rhel86 sshd[24024]: debug3: send packet: type 98 Oct 2 11:51:00 rhel86 sshd[24024]: debug1: session_exit_message: release channel 0 Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: write failed Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: chan_shutdown_write (i0 o0 sock -1 wfd 16 efd 19 [read]) Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: send eow Oct 2 11:51:00 rhel86 sshd[24024]: debug3: send packet: type 98 Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: output open -> closed Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: read<=0 rfd 17 len 0 Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: read failed Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: chan_shutdown_read (i0 o3 sock -1 wfd 17 efd 19 [read]) Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: input open -> drain Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: read 0 from efd 19 Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: closing read-efd 19 Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: ibuf empty Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: send eof Oct 2 11:51:00 rhel86 sshd[24024]: debug3: send packet: type 96 Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: input drain -> closed Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: send close Oct 2 11:51:00 rhel86 sshd[24024]: debug3: send packet: type 97 Oct 2 11:51:00 rhel86 sshd[24024]: debug2: notify_done: reading Oct 2 11:51:00 rhel86 sshd[24024]: debug3: channel 0: will not send data after close Oct 2 11:51:00 rhel86 sshd[24024]: debug3: receive packet: type 97 Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: rcvd close Oct 2 11:51:00 rhel86 sshd[24024]: debug3: channel 0: will not send data after close Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: is dead Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: gc: notify user Oct 2 11:51:00 rhel86 sshd[24024]: debug1: session_by_channel: session 0 channel 0 Oct 2 11:51:00 rhel86 sshd[24024]: debug1: session_close_by_channel: channel 0 child 0 Oct 2 11:51:00 rhel86 sshd[24024]: Close session: user sugimura from 192.168.0.130 port 35746 id 0 Oct 2 11:51:00 rhel86 sshd[24024]: debug3: mm_audit_end_command entering command /bin/sh -c 'echo PLATFORM; uname; echo FOUND; command -v '"'"'python3.10'"'"'; command -v '"'"'python3.9'"'"'; command -v '"'"'python3.8'"'"'; command -v '"'"'python3.7'"'"'; command -v '"'"'python3.6'"'"'; command -v '"'"'python3.5'"'"'; command -v '"'"'/usr/bin/python3'"'"'; command -v '"'"'/usr/libexec/platform-python'"'"'; command -v '"'"'python2.7'"'"'; command -v '"'"'/usr/bin/python'"'"'; command -v '"'"'python'"'"'; echo ENDFOUND && sleep 0' Oct 2 11:51:00 rhel86 sshd[24024]: debug3: mm_request_send entering: type 116 Oct 2 11:51:00 rhel86 sshd[24024]: debug3: session_unused: session id 0 unused Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: gc: user detached Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: is dead Oct 2 11:51:00 rhel86 sshd[24024]: debug2: channel 0: garbage collecting Oct 2 11:51:00 rhel86 sshd[24024]: debug1: channel 0: free: server-session, nchannels 1 Oct 2 11:51:00 rhel86 sshd[24024]: debug3: channel 0: status: The following connections are open:\r\n #0 server-session (t4 r2 i3/0 o3/0 e[closed]/0 fd -1/-1/-1 sock -1 cc -1)\r\n このときに着目するのは主にこのようなものを見ていきます。 SSH の type channel の open と close 送信と受信 (read, write) SSH の type は、RFC 4250 に Message Numbers として定義されています。channel ではこのあたりを追いかけます。 SSH_MSG_CHANNEL_OPEN 90 SSH_MSG_CHANNEL_EOF 96 SSH_MSG_CHANNEL_CLOSE 97 SSH_MSG_CHANNEL_REQUEST 98 SSH_MSG_CHANNEL_SUCCESS 99 datatracker.ietf.org channel については RFC 4254 も同様に見ていきます。キーボードの入出力以外にも複数のやりとりを見えないところでできるようになっています。 datatracker.ietf.org 上のログからはこのような形で読み解いていきます。 type 90 の要求に基づいて channel 0 を開く クライアントと channel 0 を使うことを合意 mm_audit_run_command によるコマンド要求を受け付け channel 0 上で読み書き 終わったら session_close_by_channel で channel 0 を閉じる mm_audit_end_command でコマンド実行を終了 実際に問い合わせのあった事例だと、この type 90 のパケットは受信したもののネゴシエーションに至らず、channel が確立できなかったために Ansible のモジュール実行の入出力が正しく受け渡しできなかったことにより、コントロールノード側ではデータの受け取り待ちになってタイムアウトしたということがありました。 ssh コマンドでログインしただけですと新しく channel を確立することがないため、当初は問題に気がつくことはありませんでした。その後例えば vim コマンドでファイルを作成したりするとターミナル上で見えている画面の書き換えのために同様に新しく channel を確立する処理が実行されることがわかり、問題の特定や再現に役に立ったのを覚えています。 これが冒頭の例に挙げた「22/tcp は許可されているものの、必要な通信を中継機器で遮断している」によって発生したもので、VPN 装置側で全ての SSH の通信パケットを通していなかったことが疑わしいということで問題解消となりました。 まとめ 最も簡単な ping モジュールでの処理について、一通り何が行われているかを紹介しました。 SSH で接続してファイルを送受信したりコマンドの実行結果を受け取るということでも、これだけの深い内容があります。さらに認証や鍵交換なども含めるとそれぞれ掘り下げることがあります。 実際の数千以上あるモジュールではそれぞれ何らかの自動化の処理を実行しますので、トラブルの内容によってはその中身も追いかけることがあります。ここまでパケットのレベルまで見ていくことはあまり頻度は多くありませんが、問題によってさまざまな方法を試しています。 Ansible Automation Platform の評価ライセンスリクエストはこちらからご利用いただけます。60日間利用できます。 www.redhat.com Happy Automation!
Red Hat のソリューションアーキテクトの杉本です。 Red Hat にはクラウドネイティブアプリの開発に役立つ、ソフトウェアのラインナップが充実していることをご存知ですか? ここでは、それらのソフトウェア [Red Hat Application Services] に関する注目ニュースを、各製品技術に特化したソリューションアーキテクトがご紹介していきます。 今回は 2022/10月号となります。 想定読者 エンタープライズアーキテクト アプリケーション開発者 システムの提案をするSIerの方 特集1: Cryostat Kubernetes 上で動作する Java で書かれたアプリケーションの監視や分析に困っていませんか?Red Hat では Java に標準で含まれている JDK Flight Recorder を簡単に使用・管理できるツールとして Cryostat を開発しています。これらは Red Hat OpenShift Platform 上だけではなく、Kubernetes でも動作可能です。 Cryostat を導入することで Java コンテナでどこが問題になっているかを簡単に収集できるようになりますので、是非ご利用下さい。 赤帽エンジニアブログではたくさんの記事を取りそろえておりますので、以下のまとめ記事からごらん下さい。 rheb.hatenablog.com 特集2: アプリケーション・モダナイゼーション: マイクロサービス間のデータ同期 マイクロサービスについて、前回はそのアーキテクチャの概要から利点、そして課題についてまとめました。 今回はマイクロサービス間のデータ同期の手法についてご紹介していきます。 rheb.hatenablog.com 特集3: Red Hatの新しいマネージドサービス "Red Hat OpenShift API Designer" を活用して同期・非同期に対応したAPIをグラフィカルに設計しよう! グラフィカルなAPI設計ツールがRed Hatの新しいマネージドサービスとしてサービスプレビューとしてリリースされました。以下のブログ記事では、いち早くそのRed Hat OpenShift API Designerの概要と使い方について紹介しています。 rheb.hatenablog.com Red Hat Summit: Connect | Japan 2022 年10月27日(木) に開催決定! Red Hat Forum が Red Hat Summit: Connect | Japan と名前を変え3年ぶりに対面イベントとして開催されることになりました! 以下のページからイベントの参加登録が可能です。ぜひ登録して、イベントにご参加ください! 参加費は無料です。 www.redhat.com 直近GAになった製品群 最近リリースとなった Application Services 製品群のリストをご紹介します。 見逃している製品が無いか、是非一度ご確認ください。 Red Hat Debezium 1.9 Red Hat 3scale API Management 2.12.0 Red Hat Process Automation Manager 7.13.0 Camel for Spring Boot 3.14
こんにちは、Red Hatでソリューションアーキテクトをしている石川です。 OpenShiftを含むKubernetesのクラスタを設計する際に悩まれるポイントの一つとして、ノードのサイジングがあると思います。 実際に必要となるノードのサイズは、その上で動かすコンテナアプリに依存するため、その要件を確認した上で決める必要がありますが、一つ抑えておきたいポイントとしてノードの保有するCPU、メモリを全てコンテナアプリで使用することができるわけではないということです。 この時関係してくるのがkubeletのsystem-reservedという設定であり、今回はOpenShiftにおいてこのsystem-reservedがどのように設定されるのかを紹介したいと思います。 Kubernetesのノードでコンテナに割り当て可能なリソース Kubernetesの公式ドキュメント を見ると、ノードのリソースのうち、実際にPodに割り当て可能な領域は以下の allocatable で表される領域であると書かれています。 式として表すと、 割り当て可能なリソース(allocatable) = ノードのリソース - kube-reserved - system-reserved - eviction-threshold ですね。 ここで登場する、 kube-reserved 、 system-reserved 、 eviction-threshold はノードで動かしているkubeletの設定オプションになります。(正確には eviction-threshold は --eviction-hard というオプションで指定します。) Kubernetesクラスタの一部としてノードを動かす場合、kube-api-serverと通信するためのkubeletと、実際にコンテナの操作を担うコンテナランタイム(OpenShiftの場合cri-o)が必要となります。 仮にノードのリソースを全てコンテナ向けに割り当てると考えると、kubeletやcri-oを動かすためのリソースが圧迫されてしまい、結果としてそのノード上で動作するコンテナ全体に影響を及ぼしてしまう(最悪、全てのコンテナが停止する)ことが考えられます。 そのためkubeletやcri-oなどのプロセスが安定的に稼働できるよう、ノード上であらかじめ必要なリソースを確保する仕組みとして kube-reserved 、 system-reserved 、 eviction-threshold が存在しています。 kube-reserved、system-reserved、eviction-thresholdについて 再び 公式ドキュメント を見るとそれぞれの設定について説明があります。 kube-reservedは、kubeletやコンテナランタイムなど、 system-reservedはsshdやudevなどのリソース確保を目的とした設定項目となっています。 それぞれでCPU、Memory、ephemeral-storageの値を入力可能です。 kube-reservedとsystem-reservedはそれぞれ kube-reserved-cgroup と system-reserved-cgroup というオプションと一緒に使用することができ、ここでcgroupを指定するとそのための占有リソースが確保されるようになります。 もしかするとここで、おや、と思われる方もいるかも知れません。というのもkube-reservedもsystem-reservedも、結局は kube-reserved-cgroup と system-reserved-cgroup で指定したcgroupを見て、それらのためのリソースを確保するため、設定項目の名前は違うもののできることは同じになるからです。 またこの kube-reserved-cgroup と system-reserved-cgroup は設定しないことも可能です。 その場合特定のcgroup向けのリソースを確保するのではなく、単純にPodに割り当て可能なリソースにkube-reservedとsystem-reservedで設定した値が含まれなくなるという形になります。 eviction-thresholdはこれらとはまた違う役割を持っています。 あるノードのMemoryが、その上で動くPodもしくはkubelet等のプロセスにより全て使われてしまうとSystem OOMsの状態となり、ノード上で動いている全てのPodの動作に影響を与えることが考えられます。 そのためkubeletの eviction-hard という設定では、現在利用可能なリソースの値が eviction-hard を下回ったら、そのノード上で動いているPodを停止させることで、リソース枯渇を防ぐという動作を取るようになります。この時どのPodが停止するかは PodのPriority等により決定 されます。気をつけたい点として、この時のPodの停止は通常のGraceful Shutdownではないため注意が必要です。 kubeletではデフォルトでこの eviction-hard が設定されています。 kubernetes.io OpenShiftでの設定 ここまでご紹介した各設定項目をOpenShiftでどのように設定しているかについては、以下のドキュメントにまとまっています。 docs.openshift.com OpenShiftではkube-reservedについては設定をしておらず、system-reservedのみを設定しています。これは先ほど書いたように、実際にはこの二つの項目でできることは同じであるため、system-reservedのみを使用しているのだと考えられます。 system-reservedで具体的にどれだけのリソースが確保されるかは、利用しているOpenShiftの環境により異なってきます。 OpenShiftではバージョン4.8より、system-reservedの値を使用しているノードのリソースから自動計算するという機能が追加されています。 docs.openshift.com こちらはSelf ManagedなOpenShiftではデフォルトでオフとなっているのですが、例えばManged OpenShiftのROSAなどではデフォルトでこの機能が有効化されており、自動で計算された値がsystem-reservedに設定されています。 この時の計算方法は以下のナレッジベースに記載があるため、気になる方はご確認下さい。 access.redhat.com ここからは実際の設定値を確認していきましょう。今回はsystem-reservedの自動計算がオンになっている環境を使っています。 まず oc proxy コマンドを実行しておきます。 oc proxy そうしたら別のターミナルを立ち上げ、 oc get node でノードの情報を取得します。 oc get node --- NAME STATUS ROLES AGE VERSION ip-10-0-155-114.us-east-2.compute.internal Ready worker 16h v1.22.8+9e95cb9 ip-10-0-158-172.us-east-2.compute.internal Ready master 16h v1.22.8+9e95cb9 ip-10-0-162-108.us-east-2.compute.internal Ready master 16h v1.22.8+9e95cb9 ip-10-0-168-39.us-east-2.compute.internal Ready worker 16h v1.22.8+9e95cb9 ip-10-0-207-222.us-east-2.compute.internal Ready master 16h v1.22.8+9e95cb9 あとは以下のcurlコマンドを実行することでノード上で動いているkubeletの設定を確認することができます。 curl -sSL "http://localhost:8001/api/v1/nodes/${NODE_NAME}/proxy/configz" | jq . 実行結果 (クリックで展開) { "kubeletconfig": { "enableServer": true, "staticPodPath": "/etc/kubernetes/manifests", "syncFrequency": "1m0s", "fileCheckFrequency": "20s", "httpCheckFrequency": "20s", "address": "0.0.0.0", "port": 10250, "tlsCipherSuites": [ "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" ], "tlsMinVersion": "VersionTLS12", "rotateCertificates": true, "serverTLSBootstrap": true, "authentication": { "x509": { "clientCAFile": "/etc/kubernetes/kubelet-ca.crt" }, "webhook": { "enabled": true, "cacheTTL": "2m0s" }, "anonymous": { "enabled": false } }, "authorization": { "mode": "Webhook", "webhook": { "cacheAuthorizedTTL": "5m0s", "cacheUnauthorizedTTL": "30s" } }, "registryPullQPS": 5, "registryBurst": 10, "eventRecordQPS": 5, "eventBurst": 10, "enableDebuggingHandlers": true, "healthzPort": 10248, "healthzBindAddress": "127.0.0.1", "oomScoreAdj": -999, "clusterDomain": "cluster.local", "clusterDNS": [ "172.30.0.10" ], "streamingConnectionIdleTimeout": "4h0m0s", "nodeStatusUpdateFrequency": "10s", "nodeStatusReportFrequency": "5m0s", "nodeLeaseDurationSeconds": 40, "imageMinimumGCAge": "2m0s", "imageGCHighThresholdPercent": 85, "imageGCLowThresholdPercent": 80, "volumeStatsAggPeriod": "1m0s", "systemCgroups": "/system.slice", "cgroupRoot": "/", "cgroupsPerQOS": true, "cgroupDriver": "systemd", "cpuManagerPolicy": "none", "cpuManagerReconcilePeriod": "10s", "memoryManagerPolicy": "None", "topologyManagerPolicy": "none", "topologyManagerScope": "container", "runtimeRequestTimeout": "2m0s", "hairpinMode": "promiscuous-bridge", "maxPods": 250, "podPidsLimit": -1, "resolvConf": "/etc/resolv.conf", "cpuCFSQuota": true, "cpuCFSQuotaPeriod": "100ms", "nodeStatusMaxImages": 50, "maxOpenFiles": 1000000, "contentType": "application/vnd.kubernetes.protobuf", "kubeAPIQPS": 50, "kubeAPIBurst": 100, "serializeImagePulls": false, "evictionHard": { "imagefs.available": "15%", "memory.available": "100Mi", "nodefs.available": "10%", "nodefs.inodesFree": "5%" }, "evictionPressureTransitionPeriod": "5m0s", "enableControllerAttachDetach": true, "makeIPTablesUtilChains": true, "iptablesMasqueradeBit": 14, "iptablesDropBit": 15, "featureGates": { "APIPriorityAndFairness": true, "DownwardAPIHugePages": true, "LegacyNodeRoleBehavior": false, "NodeDisruptionExclusion": true, "RotateKubeletServerCertificate": true, "ServiceNodeExclusion": true, "SupportPodPidsLimit": true }, "failSwapOn": true, "memorySwap": {}, "containerLogMaxSize": "50Mi", "containerLogMaxFiles": 5, "configMapAndSecretChangeDetectionStrategy": "Watch", "systemReserved": { "cpu": "0.11", "memory": "5.3Gi" }, "enforceNodeAllocatable": [ "pods" ], "volumePluginDir": "/etc/kubernetes/kubelet-plugins/volume/exec", "logging": { "format": "text" }, "enableSystemLogHandler": true, "shutdownGracePeriod": "0s", "shutdownGracePeriodCriticalPods": "0s", "enableProfilingHandler": true, "enableDebugFlagsHandler": true, "seccompDefault": false, "memoryThrottlingFactor": 0.8 } } この設定の中で"evictionHard"や"systemReserved"として値が設定されているのを確認できます。 "evictionHard"を見るとkubeletのデフォルトの設定値がOpenShiftでも適用されているのがわかります。 "evictionHard": { "imagefs.available": "15%", "memory.available": "100Mi", "nodefs.available": "10%", "nodefs.inodesFree": "5%" }, "systemReserved"には自動計算された値が設定されています。 今回はノードのメモリが約62GiBなので比較的大きな値がここに設定されているのがわかります。 "systemReserved": { "cpu": "0.11", "memory": "5.3Gi" }, また設定値を確認すると"systemReservedCgroup"が設定されていません。 つまり、OpenShiftではkubeletに --system-reserved-cgroup を設定しておらず、特定のcgroupにリソースを割り振る動きをしていないということがわかります。 自分で割り当て可能なリソースを計算してみる ここまでで実際の設定値も含めてsystem-reserved等について確認することができました。 折角なので、メモリを対象としてPodに割り当て可能なリソースを自分で計算したいと思います。 まずノード全体のメモリの値を以下のコマンドで取得します。 oc get node ${NODE_NAME} -o jsonpath="{.status.capacity.memory}" --- 64790784Ki この値から先ほど確認したsystem-reservedのメモリの値とevicition-hardの値を引けば割り当て可能リソースになるはずです。 単位をKiBに合わせると、 となります。 答え合わせのため、以下のコマンドを実行しましょう。 割り当て可能なリソースはノードの .status.allocatable から確認できます。 oc get node ${NODE_NAME} -o jsonpath="{.status.allocatable.memory}" --- 60550073548800m 単位をKiBに合わせると ということで無事正しい値を計算できたことが確認できました。 まとめ 今回はOpenShiftのsystem-reservedの設定についてご紹介しました。 system-reserved等の設定はノードで実際にどれだけのリソースを使うことができるのかということを理解する上で大事なポイントになります。 またOpenShiftにはノードを安定して運用するためのsystem-reservedの自動計算機能もありますので、本記事を見てご興味持たれたらぜひこちらも試してみて下さい。
こんにちは。Red Hatでソリューションアーキテクトをしている杉本 拓です。 Red Hatのマネージドサービスについては 以前の赤帽ブログ でも紹介したように、マネージドのOpenShift環境で様々なマネージドサービスが利用可能になっています。 Application Services: Red Hat OpenShift API Management Red Hat OpenShift Streams for Apache Kafka Red Hat OpenShift Service Registry Red Hat OpenShift Connectors Data Services: Red Hat OpenShift Data Science Red Hat OpenShift Database Access 今回は上記に加えて新しく追加されたマネージドのApplication Servicesの一つである Red Hat OpenShift API Designer について紹介したいと思います(この記事執筆時点の2022年9月ではService Previewです)。 Red Hat OpenShift API Designerとは OpenShift API DesignerのベースとなっているOSSは Apicurio Studio で、 以前のニュースレター でも少し紹介をさせて頂いています。OpenShift API Designerは、Red Hatのマネージドサービスの利用者で Red Hat Hybrid Cloud Console にアカウントがあれば誰でも無料で利用可能なサービスとなっており、ブラウザを使用してGUIベースでOpenAPI (Swagger) に準拠したAPIドキュメント作成したり、JSON Schemaなどのスキーマを簡単に作成できるようにするためのツールです。APIドキュメントをグラフィカルに定義するためのツールは他にもありますが、このRed Hat OpenShift API Designerでは、REST APIの仕様を記述するためのOpenAPIだけでなく、イベント駆動型アーキテクチャにおける非同期なAPIを定義するためのAsyncAPIにも対応しており、Apache Avro/JSON Schema/Google Protocol Buffers (Protobuf)などのスキーマもブラウザベースで定義することができるようになっており、それがマネージドサービスとして利用できるようになっています。 このOpenShift API Designerの主なユースケースとしては以下の2つになります: コントラクト (API) ファーストによるアプリケーション開発 Kafkaのクライアントアプリケーションで利用されるスキーマの定義 さらに以下のようなRed Hatの別のマネージドサービスと一緒に利用することでハイブリッドクラウドな環境で同期・非同期のAPIに対応したAPI設計をグラフィカルに簡単に行うことができるようになります。 Red Hat OpenShift Service Registry : Red Hat OpenShift Service Registryと連携することで、OpenShift Service RegistryからAPIドキュメントやスキーマ定義をOpenShift API Designerにインポートして編集したり、OpenShift API Designerで作成したAPIドキュメントやスキーマ定義をOpenShift Service Registryにエクスポートすることができるようになるため、OpenShift Service RegistryでAPIドキュメントやスキーマ定義を一元管理することができるようになっています。 Red Hat OpenShift API Management : OpenShift API Designerで作成したAPIドキュメントをAPI管理のサービスであるOpenShift API Managementに取り込んで、そのAPIを管理していくことができます。 Red Hat OpenShift Streams for Apache Kafka : Red HatのマネージドのKafkaであるOpenShift Streams for Apache KafkaでProduce/Consumeされるメッセージのスキーマを、OpenShift API Designerで編集することができるようになります(Service Previewでは未実装)。 Red Hat OpenShift API Designerの使い方 では、このOpenShift API Designerをどのようにして使うのか、その使い方を簡単に紹介していきましょう。前提として Red Hat Hybrid Cloud Console でのRed Hatアカウントが必要となるため、もしまだアカウントをお持ちでない場合は Red Hat Hybrid Cloud Console の画面に従ってアカウントを作成してください。 Red Hat Hybrid Cloud Console にログインして左の Application and Data Services メニューを選択すると、以下のような画面が表示され、各マネージドサービスはここからアクセスできるようになっています。 左のメニューからAPI DesignerをクリックしてAPIドキュメントとスキーマ定義を行っていくことができます。なおこの記事執筆時の2022年9月ではService Previewのため、ベータ環境で利用するための確認のダイアログが表示されます。確認して次に進むとAPI Designerの画面が表示されます。 [Create design]をクリックすると、APIとスキーマの新規作成画面が表示されます。 ここでは試しにPet Store Exampleを選択し、OpenAPIのドキュメントを作成してみましょう。TypeのリストからOpenAPIを選択し、NameやDescription の値は任意で、Version はデフォルトのままで構いません。 この例ではAPIドキュメントのNameの値としてMy Pet Store APIを設定し、APIドキュメントを作成していますので、以下のようにMy Pet Store API という名前のAPIドキュメントが作成されます。 以下のようにAPIのURLパスの /pets を選択すると、そのAPIのエンドポイントごとにパラメータや戻り値など、YAMLやJSONを編集しなくてもAPIの詳細を設定していくことができるようになっています。 APIドキュメントを保存したら、以下のようにメニューから[Export design to Service Registry]を選択することで、そのAPI仕様やスキーマ定義をRed Hat OpenShift Service Registryにエクスポートすることができるようになっています。 実際に[Export design to Service Registry]を選択すると、以下のようにService Registryのインスタンスを選択することができるようになります。ただしService Registryは事前にインスタンスが作成されていることが前提となります(以下の画面ではmy-service-registryという名前のRegistryインスタンスを使用)。 エクスポートの設定として、Group/ID/Versionに任意の値を指定することが可能です。特に指定しない場合、Groupはデフォルトグループが選択され、IDとVersionの値は自動生成されます。 実際にエクスポートすると、Hybrid Cloud ConsoleのService Registryの画面で先程エクスポートしたMy Pet Store APIがmy-service-registryというService Registryのインスタンスに格納されていることが分かります。 Service Registryの画面でもAPI仕様のドキュメントが画面に表示され、Service Registryに格納されているAPIドキュメントやスキーマ定義の情報はService Registryで確認し、バージョン管理することができるようになっています。 上のステップではAPI DesignerでAPIドキュメントを作成し、それをService Registryにエクスポートする手順を示していましたが、既にAPIドキュメントが存在している場合には、Service Registry/APIドキュメントのURL/ファイルからAPI DesignerにインポートしてそのAPIドキュメントを編集することが可能になっています。 最後に 以上のように、API Designerを使うことでAPIの設計をグラフィカルに簡単に行うことができ、さらにService Registryと連携させることで企業内でのAPIの標準化を推進することができるようになります。また同期通信のためのOpenAPIだけではなく、非同期通信のためのAsyncAPIにも対応しているので、REST APIとイベント駆動型アーキテクチャを適材適所で使い分けて設計や管理は一元的に行うことができるようになります。Hybrid Cloud ConsoleにアカウントがあればAPI DesignerもService Registryも無料で使うことができるのでぜひ試してみてください。その他のRed Hatのクラウドサービスも無料で試すことができるのでぜひ一緒に使っていただければと思います。 参考情報 Red HatのApplication & Dataのマネージドサービスについては、以下の赤帽ブログの記事もご参照ください。 クラウドネイティブなアプリケーション開発を始めよう! 〜 すぐに試せるRed Hatのマネージドサービス 〜 - 赤帽エンジニアブログ OpenShift API ManagementをDeveloper Sandboxで試してみよう! - 赤帽エンジニアブログ Red HatによるManaged Kafka, RHOSAKのトライアルを試してみる! - 赤帽エンジニアブログ Red HatのManaged KafkaにQuarkusアプリケーションで接続してみよう! - 赤帽エンジニアブログ Red Hat OpenShift Connectors のご紹介 - 赤帽エンジニアブログ Red Hat OpenShift Data Science を好きになる4つの理由 - 赤帽エンジニアブログ
こんにちは。レッドハットでストレージを中心にクラウドインフラを生業にしている宇都宮(うつぼ)です。 なんだか久しぶりな感じがしますが、今回はOpenShift Data Foundation(ODF)のMultiCloud Gateway(MCG)について話します。 以前(だいぶ前)にもMCG自体については紹介したのですが、今回はそんなMCGにまつわる実践的な使い方を紹介します。 MCGってなんだという方は、こちらのちょっとアレなノリの記事をご参照ください。 参考: https://rheb.hatenablog.com/entry/ocs/01 https://rheb.hatenablog.com/entry/ocs/02 OpenShiftはオブジェクトストレージを使う OpenShiftを使うと地味にオブジェクトストレージを求められることがあります。代表的なものはコンテナレジストリのバックエンドです。 例えば、Red Hatのコンテナレジストリのソフトウェアに、Red Hat Quayというものがありますが、このQuayはバックエンドにオブジェクトストレージを必要とします。 参考: https://rheb.hatenablog.com/entry/202002-quay また、OpenShiftの内部レジストリ(OpenShift Image Registry)のバックエンドも、Persistent Volumeでも構成可能ではありますがオブジェクトストレージがRecommendedです。 https://docs.openshift.com/container-platform/4.11/scalability_and_performance/optimizing-storage.html パブリッククラウドでOpenShiftを使っているならクラウドストレージサービスを使えば話は早いのですが、オンプレだとそう簡単ではありません。 そういう場合にMCGは便利だったりします。 MCGはPVをバックエンドにバケットを提供できる 以前紹介した内容を見ると、MCGは他のオブジェクトストレージのバケットを仮想的に統合するゲートウェイ、のような説明をしています。 そうすると、「結局バックエンドにあらかじめオブジェクトストレージが要るんじゃないかよ、何がやりたいんだコラ」と思いますよね。ナニコラタココラと。 実はMCGは進化して、PVをバックエンドにしてバケットを提供できるようになっています。 まずはPVの源泉となるStorage Classを指定して BackingStore というカスタムリソースを作ります。そしてこれを使う BucketClass を作って、後は OBC(ObjectBucketClaim) を投げるだけでサクッとバケットが作れます。まあカンタン! BackingStoreの作成 BucketClassの作成 OBC(ObjectBucketClaim)の作成 作成したBucketへのアクセス情報が表示される ODFはMCGだけインストールするオプションがある MCGはODFの中に含まれている機能の1つなので、ODFをインストールすることでMCGが使えるようになります。 しかし、「ぶっちゃけPVには困ってないからRook-Cephは必要ないんだよなぁCPUもメモリも食うし。MCGだけ使いたいんだよなぁ」みたいな場合もあるでしょう。 ええ、実はODFはMCGだけデプロイすることができます。 ODF Operatorをインストールして、 StorageSystem を作る時に、デプロイメントタイプに"MultiCloud Object Gateway"を選択して StorageSystem を作りましょう。 MCGだけインストールするオプション そうするとこんな具合にMCGだけデプロイされます。 $ oc get pod -n openshift-storage NAME READY STATUS RESTARTS AGE csi-addons-controller-manager-cd76d866d-slfp7 2/2 Running 0 6m28s noobaa-core-0 1/1 Running 0 2m9s noobaa-db-pg-0 1/1 Running 0 2m10s noobaa-endpoint-768dfc9bd6-57jk8 1/1 Running 0 47s noobaa-operator-5bb8ccb9b-gp5h5 1/1 Running 1 (2m9s ago) 6m44s ocs-metrics-exporter-7fffdc5cf5-c6wsf 1/1 Running 0 6m21s ocs-operator-68ccfbd9dd-7gbln 1/1 Running 0 6m42s odf-console-d5fc456fd-xbtm4 1/1 Running 0 6m41s odf-operator-controller-manager-546574b575-j2crh 2/2 Running 0 6m41s rook-ceph-operator-6685c78555-6hjvq 1/1 Running 0 6m42s $ oc get pvc -n openshift-storage NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE db-noobaa-db-pg-0 Bound pvc-61f14200-5bf2-4670-b295-3421ec5d2733 50Gi RWO gp3-csi 6m48s なんにも使われてないrook-ceph-operatorが哀愁を漂わせますね。 ちなみに noobaa-db のPVは、Rook-Cephがある場合はそこから作られますが、ない場合は StorageSystem を作るときに指定したStorage Classから作られます。 MCG専用のGUI ちゃんとGUIも使えます。 レジストリとMCGを連携させることができる さてここからが本題でレジストリの話に戻るのですが、Red Hat Quayも内部レジストリも、MCGと連携してオブジェクトバケットをデータストアとして置くことができます。 QuayとMCGの連携 特にRed Hat Quayは、MCGが居るOpenShiftクラスタでQuay Operatorを使って構成すると、バックエンドストレージの設定を何もしなくても自動的にMCGのバケットを作って設定してくれます。 やってみましょう。 Quay Operatorをインストールして、 Quay Operatorのインストール レジストリを動かす適当なプロジェクトを作って、 QuayRegistry カスタムリソースを作ります。 この段階で、"config bundle"というQuayの設定パラメータをSecretの形で作成して適用することで、色々とカスタマイズすることができます。 今はオブジェクトバケットが自動的にできているかどうかだけを見たいので、リソースの名前以外は全部デフォルトで進めます。 QuayRegistryカスタムリソースの作成 10-15分くらい待っていると、レジストリが自動的にでき上がります。 $ oc get all -n my-quay NAME READY STATUS RESTARTS AGE pod/my-registry-clair-app-59d65b9c8d-5hvjf 1/1 Running 0 8m21s pod/my-registry-clair-app-59d65b9c8d-c6zcq 1/1 Running 0 7m6s pod/my-registry-clair-app-59d65b9c8d-chpb6 1/1 Running 0 8m21s pod/my-registry-clair-app-59d65b9c8d-dw8w4 1/1 Running 3 (14m ago) 15m pod/my-registry-clair-app-59d65b9c8d-knfvt 1/1 Running 0 7m51s pod/my-registry-clair-app-59d65b9c8d-m9sw7 1/1 Running 0 7m51s pod/my-registry-clair-app-59d65b9c8d-tg2gf 1/1 Running 3 (14m ago) 14m pod/my-registry-clair-postgres-5476fbcbcf-7snc4 1/1 Running 1 (14m ago) 15m pod/my-registry-quay-app-84cd84c546-9cr4g 1/1 Running 0 13m pod/my-registry-quay-app-84cd84c546-vsktj 1/1 Running 0 13m pod/my-registry-quay-app-upgrade--1-fwvpz 0/1 Completed 0 14m pod/my-registry-quay-config-editor-5b8985df88-kmkrg 1/1 Running 0 14m pod/my-registry-quay-database-7bd56b69d7-6vgrh 1/1 Running 0 15m pod/my-registry-quay-mirror-584f5d6fbc-c5hq4 1/1 Running 0 14m pod/my-registry-quay-mirror-584f5d6fbc-z7jg4 1/1 Running 0 14m pod/my-registry-quay-redis-67854cb5fd-nwdp6 1/1 Running 0 15m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/my-registry-clair-app ClusterIP 172.30.111.209 <none> 80/TCP,8089/TCP 15m service/my-registry-clair-postgres ClusterIP 172.30.138.193 <none> 5432/TCP 15m service/my-registry-quay-app ClusterIP 172.30.22.33 <none> 443/TCP,80/TCP,8081/TCP,55443/TCP 15m service/my-registry-quay-config-editor ClusterIP 172.30.24.78 <none> 80/TCP 15m service/my-registry-quay-database ClusterIP 172.30.130.162 <none> 5432/TCP 15m service/my-registry-quay-metrics ClusterIP 172.30.11.12 <none> 9091/TCP 15m service/my-registry-quay-redis ClusterIP 172.30.143.50 <none> 6379/TCP 15m NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/my-registry-clair-app 7/7 7 7 15m deployment.apps/my-registry-clair-postgres 1/1 1 1 15m deployment.apps/my-registry-quay-app 2/2 2 2 15m deployment.apps/my-registry-quay-config-editor 1/1 1 1 15m deployment.apps/my-registry-quay-database 1/1 1 1 15m deployment.apps/my-registry-quay-mirror 2/2 2 2 15m deployment.apps/my-registry-quay-redis 1/1 1 1 15m NAME DESIRED CURRENT READY AGE replicaset.apps/my-registry-clair-app-59d65b9c8d 7 7 7 15m replicaset.apps/my-registry-clair-postgres-5476fbcbcf 1 1 1 15m replicaset.apps/my-registry-quay-app-6d7b68678d 0 0 0 15m replicaset.apps/my-registry-quay-app-84cd84c546 2 2 2 14m replicaset.apps/my-registry-quay-config-editor-5b8985df88 1 1 1 14m replicaset.apps/my-registry-quay-config-editor-5d9c485579 0 0 0 15m replicaset.apps/my-registry-quay-database-7bd56b69d7 1 1 1 15m replicaset.apps/my-registry-quay-mirror-584f5d6fbc 2 2 2 14m replicaset.apps/my-registry-quay-mirror-68cd8d4b87 0 0 0 15m replicaset.apps/my-registry-quay-redis-67854cb5fd 1 1 1 15m NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE horizontalpodautoscaler.autoscaling/my-registry-clair-app Deployment/my-registry-clair-app 75%/90%, 1%/90% 2 10 7 15m horizontalpodautoscaler.autoscaling/my-registry-quay-app Deployment/my-registry-quay-app 30%/90%, 1%/90% 2 20 2 15m horizontalpodautoscaler.autoscaling/my-registry-quay-mirror Deployment/my-registry-quay-mirror 25%/90%, 0%/90% 2 20 2 15m NAME COMPLETIONS DURATION AGE job.batch/my-registry-quay-app-upgrade 1/1 12s 14m NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD route.route.openshift.io/my-registry-quay my-registry-quay-my-quay.apps.cluster-px6nv.px6nv.sandbox906.opentlc.com my-regis try-quay-app http edge/Redirect None route.route.openshift.io/my-registry-quay-builder my-registry-quay-builder-my-quay.apps.cluster-px6nv.px6nv.sandbox906.opentlc.com my-regis try-quay-app grpc edge/Redirect None route.route.openshift.io/my-registry-quay-config-editor my-registry-quay-config-editor-my-quay.apps.cluster-px6nv.px6nv.sandbox906.opentlc.com my-regis try-quay-config-editor http edge/Redirect None さあここで何も設定しなかったデータストアですが、 OBC を確認してみると、 自動的にOBCが作られている。 作られています。Storage Classに "openshift-storage.noobaa.io" が指定されていることから、MCGで間違いなさそうです。 またオーナーとして QuayRegistry が指定されているので、Quayが作った OBC であることはがわかります。 もちろん自分で作った OBC をデータストアに指定することは可能です。ここではQuayとMCGが連携して動きますーという一例で、データストアの自動構成をお見せしました。 内部レジストリとMCGの連携 内部レジストリとMCGは、Quayのように自動では連携することはしません。 こちらのドキュメントの手順のように OpenShift Image Registry Operator を使って手動で構成できます。 https://docs.openshift.com/container-platform/4.11/registry/configuring_registry_storage/configuring-registry-storage-rhodf.html#registry-configuring-registry-storage-rhodf-nooba_configuring-registry-storage-rhodf まとめ ODFは現在 "OpenShift Platform Plus(OPP)" の一員として含まれており、OpenShiftのストレージやデータ系のインフラサービスを提供する基盤として存在感を日々高めています(うつぼにとっては)。 レジストリだけではなく、バックアップ/リストアのOADPや、マイグレーションのMTC、マルチクラスタのACMなど、インフラ側のツールとバリバリ連携が進んでいます。今後はApplication側とも積極的に連携していくでしょう(うつぼの期待では)。 そんな具合で、今後もOpenShift好きな方はODF/MCGから目が離せないですね!ハハハッ! というわけで今回はここまで。
レッドハットのソリューションアーキテクトの森です。 マイクロサービスについて、前回はそのアーキテクチャの概要から利点、そして課題についてまとめました。今回はマイクロサービス間のデータ同期の手法についてご紹介していきます。 前回の記事はこちらです。 rheb.hatenablog.com Outboxパターン と Change Data Capture によるデータ同期 マイクロサービスにおいては、基本的には各サービスとデータベースは1:1とするモデルを推奨しています。そのため、サービス間のデータベースの同期を取るしくみについて考慮が必要です。データベース間の同期を取るための方法の1つとして、データベース処理とメッセージを併用する Transactional Outbox と Transactionlog tailing があります。 Transactional Outbox の実体はデータベースのテーブルであり、マイクロサービスのデータを格納するデータベースの中に同居します。そして、データに対する処理を通知するイベント情報を格納し、共有するために用いられます。それらは同一のデータベースに共存しているので、ローカルトランザクションでもお互いの内容は即時に同期を取ることができます。Transaction log tailing は、Outboxテーブルのトランザクションログへのログエントリを監視をする Transaction log miner を用意します。これは、データベースのトランザクションログ上に書き出された新たなログエントリを取得し、メッセージブローカーにメッセージを発行して、後続のサービスにおけるトランザクション処理につなげます。 異常系の処理として、たとえば注文サービスによるデータ処理は成功したものの、在庫サービスの処理で何らかの不具合が発生した場合には、在庫サービスは在庫テーブルへの処理をロールバックし、Outboxテーブル、Transaction log miner、メッセージブローカーを介して在庫サービスにおけるデータ処理失敗のイベントを通知します。 トランザクションログを監視するミドルウェアとして、レッドハットでは Debezium を提供しています。これは、Kafka Connect の機能を利用したデータベースへのコネクタで、指定されたデータベースに接続し、トランザクションログを読み取り、それを Kafka のメッセージとして発行します。 上記のしくみでデータベース間の同期を取ることができますが、厳密には各データベースの整合性が取れるまでには数百ミリ秒~数秒単位のラグが発生します。つまり、常時データベース間の整合性が取れていなければならない場合には適用できませんが、「結果整合性」が許容できる場合に活用できる方法です。この結果整合性が許容できるかどうか、というのは業務ドメインのエキスパートも交えて判断する必要があります。業務ドメインのエキスパートも巻き込んで、必要とされる要件を洗い出し、データベース間の同期/整合性の設計を行っていきましょう。 Debezium をさわってみる 以下のサイトで、Debeziumを活用したソリューションの紹介と、デモの紹介をしています。(英語) redhat-solution-patterns.github.io こちらのデモについては、OpenShift環境があれば、自分で動かしてみることができます。 デモ環境のデプロイの手順や内容についてまとめましたので、興味のある方はぜひ挑戦してみてください。 speakerdeck.com
Red HatのChristian Hornです。 家電の消費電力に興味があって、僕はスマートプラグを3つ買いました。 その3つのプラグはそれぞれの携帯のアプリで消費電力の提示ができるが、 一つしかAPIの供給ができません。この記事では、そのAPIで得たデータを PCPのデータベースに保存しましょう。 ソフトをインストールする スマートプラグの設置 スクリプトでAPIを使う事 openmetricsを使う 図形にしたデータ ソフトをインストールする この記事はRed Hat Enterprise Linux 9で作られたが、FedoraやCentos Streamなども使えます。 まずは必要なソフトをインストールします。Performance Co-pilotは パーフォマンス分析でよく使われているソフトです。省略して、PCPに なります。pcp-zeroconfというパッケージをインストールすると、 定義的なシステムのパーフォマンスデータの保存が始まります。 pcp-pmda-openmetricsはPCPで使われているソフトです。 jqというソフトはjsonデータの分析ができます。 dnf -y install pcp-zeroconf pcp-pmda-openmetrics jq cd /var/lib/pcp/pmdas/openmetrics/ ./Install スマートプラグの設置 僕はSwitchBot プラグミニ スマートプラグを 買って、説明書どおりに設置して、アプリでプラグで測った消費電力の 値を読めます。 その後で、そのアプリを実行して、「プロフィール」と「設定」を クリックして、「アプリバージョン」を10回クリックして下さい。そうしたら、 「開発者向けオプション」が生じます。クリックしたら、トークンの取得が できます。これから、そのトークンを秘密のように使いましょう。 これからはスマートプラグのdeviceIdを聞きましょう。tokenとして、 取得したトークンを使って下さい。 [chris@rhel9a ~]# token="[アプリで得たトークン]" [chris@rhel9a ~]# auth="Authorization:${token}" [chris@rhel9a ~]# curl -s -H "${auth}" https://api.switch-bot.com/v1.0/devices | jq { "statusCode": 100, "body": { "deviceList": [ { "deviceId": "232323424242", "deviceName": "Plug Mini(US) 56", "deviceType": "Plug Mini (US)", "enableCloudService": true, "hubDeviceId": "" } ], "infraredRemoteList": [] }, "message": "success" } [chris@rhel9a ~]# スクリプトでAPIを使う事 これから、スクリプトでAPIを使いましょう。下記のスクリプトで 得たトークンとdeviceidを入れて下さい。 [chris@rhel9a ~]# cat switchbot.sh #!/usr/bin/bash token="[アプリで得たトークン]" auth="Authorization:${token}" deviceid="232323424242" url_list="https://api.switch-bot.com/v1.0/devices" url_status="https://api.switch-bot.com/v1.0/devices/${deviceid}/status" curl -s -H "${auth}" "${url_status}" | jq >/tmp/curl_switchbot volt=$(grep voltage /tmp/curl_switchbot | sed -e 's/,//' -e 's,.*: ,,') echo "voltage {func=\"voltage\"} ${volt}" weight=$(grep weight /tmp/curl_switchbot | sed -e 's/,//' -e 's,.*: ,,') echo "weight {func=\"weight\"} ${weight}" electricityOfDay=$(grep electricityOfDay /tmp/curl_switchbot | sed -e 's/,//' -e 's,.*: ,,') echo "electricityOfDay {func=\"electricityOfDay\"} ${electricityOfDay}" electricCurrent=$(grep electricCurrent /tmp/curl_switchbot | sed -e 's/,//' -e 's,.*: ,,') echo "electricCurrent {func=\"electricCurrent\"} ${electricCurrent}" [chris@rhel9a ~]# chmod +x switchbot.sh 実際に実行したら... [chris@rhel9a ~]# ./switchbot.sh voltage {func="voltage"} 101.5 weight {func="weight"} 32.1 electricityOfDay {func="electricityOfDay"} 27 electricCurrent {func="electricCurrent"} 5.3 [chris@rhel9a ~]# rm /tmp/curl_switchbot [chris@rhel9a ~]# openmetricsを使う このスクリプトはメーカーのAPIを使って、データを出力します。 pcp-pmda-openmetricが期待する形ですので、これからそのスクリプトを openmetricsから実行しましょう。 /var/lib/pcp/pmdas/openmetrics/config.d/というディレクトリーに移動して から、selinuxのコンテクストを設定します。 [chris@rhel9a ~]# sudo mv switchbot.sh /var/lib/pcp/pmdas/openmetrics/config.d/ [chris@rhel9a ~]# sudo chcon --reference /var/lib/pcp/pmdas/openmetrics/config.d \ /var/lib/pcp/pmdas/openmetrics/config.d/switchbot.sh これから、pcpもプラグのデータを示します: [chris@rhel9a ~]$ pmrep -g openmetrics.switchbot [ 1] - openmetrics.switchbot.electricCurrent["0 func:electricCurrent"] - none [ 2] - openmetrics.switchbot.electricityOfDay["0 func:electricityOfDay"] - none [ 3] - openmetrics.switchbot.weight["0 func:weight"] - none [ 4] - openmetrics.switchbot.voltage["0 func:voltage"] - none 1 2 3 4 0.160 83.000 0.500 101.500 0.160 83.000 0.500 101.500 0.160 83.000 0.500 101.500 0.160 83.000 0.500 101.500 ^C [chris@rhel9a ~]$ 現在のデータを保存ファイルにいれましょう: [root@rhel9a ~]# cat /etc/pcp/pmlogconf/openmetrics/switchbot #pmlogconf-setup 2.0 ident OpenMetrics switchbot force include openmetrics.switchbot [root@rhel9a ~]# cd /var/lib/pcp/config/pmlogconf/openmetrics/ [root@rhel9a openmetrics]# ln -s ../../../../../../etc/pcp/pmlogconf/openmetrics/switchbot . [root@rhel9a openmetrics]# systemctl restart pmlogger Performance Co-Pilot configuration on rhel9a.local: platform: Linux rhel9a.local 5.14.0-70.13.1.el9_0.x86_64 #1 SMP PREEMPT Thu Apr 14 12:42:38 EDT 2022 x86_64 hardware: 4 cpus, 1 disk, 1 node, 3722MB RAM timezone: JST-9 services: pmcd pmcd: Version 5.3.5-8, 12 agents, 6 clients pmda: root pmcd proc pmproxy xfs linux nfsclient mmv kvm jbd2 dm openmetrics pmlogger: primary logger: /var/log/pcp/pmlogger/rhel9a.local/20220918.17.33 pmie: primary engine: /var/log/pcp/pmie/rhel9a.local/pmie.log [root@rhel9a openmetrics]# これから、プラグの過去のデータも読めます: [root@rhel9a openmetrics]# pmrep -p -g -a /var/log/pcp/pmlogger/rhel9a.local/20220918.17.33 openmetrics.switchbot [ 1] - openmetrics.switchbot.voltage["0 func:voltage"] - none [ 2] - openmetrics.switchbot.weight["0 func:weight"] - none [ 3] - openmetrics.switchbot.electricityOfDay["0 func:electricityOfDay"] - none [ 4] - openmetrics.switchbot.electricCurrent["0 func:electricCurrent"] - none 1 2 3 4 17:33:06 N/A N/A N/A N/A 17:33:07 N/A N/A N/A N/A 17:33:08 101.100 0.000 114.000 0.000 17:33:09 101.100 0.000 114.000 0.000 17:33:10 101.100 0.000 114.000 0.000 17:33:11 101.100 0.000 114.000 0.000 図形にしたデータ 保存したデータを図形にする事もできます。これはプラグで測ったシステムとモニターの消費量です: switchbot grafana
Red Hatの森若です。 自分でsystemdのservice unitを作るときに、起動用のいくつかのコマンドを記述したシェルスクリプトを呼ぶ事は(理想的ではないですが)あるかと思います。 今回はこの場合に、sudoを利用するとまずい理由を説明して、かわりにsetprivを使うほうがよいという話です。 例題用のservice 実行してみる 別のcgroupだと何がまずいのか? 対策はsetprivコマンド 例題用のservice sudoによるまずい動作を確認するためのできるだけ単純な例として、hoge.service を用意します。 /opt/hoge/hoge.sh #!/bin/bash sudo -u moriwaka sleep 5000 /etc/systemd/system/hoge.service [Unit] Description=hoge [Service] Type=oneshot ExecStart=/opt/hoge/hoge.sh 実行してみる systemctl のデフォルトだとhoge.shの実行終了を待ってしまうので、 --no-block オプションをつけて実行します。 # systemctl --no-block start hoge.service systemctl status で状態を見てみます。ExecStartのスクリプトが実行中なのでactivatingなのは予想通りですが、実行中のはずの sleep がCGroupの中になく、bashだけが存在していることがわかります。 # systemctl status hoge ● hoge.service - hoge Loaded: loaded (/etc/systemd/system/hoge.service; static) Active: activating (start) since Wed 2022-09-21 13:42:32 JST; 8min ago Main PID: 4110436 (hoge.sh) Tasks: 1 (limit: 37738) Memory: 1.7M CPU: 10ms CGroup: /system.slice/hoge.service └─ 4110436 /bin/bash /opt/hoge/hoge.sh Sep 21 13:42:32 turtle systemd[1]: Starting hoge.service - hoge... Sep 21 13:42:32 turtle sudo[4110437]: root : PWD=/ ; USER=moriwaka ; COMMAND=/usr/bin/sleep 5000 cgroupの様子をみるときには systemd-cgls を使います。sudoとsleepが、サービスに対応する /system.slice/hoge.service ではなく /user.slice/user-1000.slice/session-c4.scope 内で実行されていることがわかります。 これはsudoで指定したユーザ(id: moriwaka, UID: 1000)のセッション「c4」が作成され、その中で実行されていることを示しています。 -.slice ├─user.slice (#1144) │ → trusted.invocation_id: 1041b6b580784b47b41c08d97ab05e23 │ └─user-1000.slice (#5335) │ → trusted.invocation_id: a9169cd85293489e9697b4d337962a86 │ ├─user@1000.service … (#5423) (中略) │ ├─session-c4.scope (#1663079) │ │ → trusted.invocation_id: d257de8a94d54a6eac713a35e228dc27 │ │ ├─ 4110437 sudo -u moriwaka sleep 5000 │ │ └─ 4110439 sleep 5000 (以下略) systemctlでscopeの状態を表示させるとこのように表示されます。 sudoがpam_unixを経由してセッションを作成した旨のログが表示されています。 # systemctl status session-c4.scope ● session-c4.scope - Session c4 of User moriwaka Loaded: loaded (/run/systemd/transient/session-c4.scope; transient) Transient: yes Active: active (running) since Wed 2022-09-21 13:42:32 JST; 13min ago Tasks: 2 Memory: 392.0K CPU: 2ms CGroup: /user.slice/user-1000.slice/session-c4.scope ├─ 4110437 sudo -u moriwaka sleep 5000 └─ 4110439 sleep 5000 Sep 21 13:42:32 turtle systemd[1]: Started session-c4.scope - Session c4 of User moriwaka. Sep 21 13:42:32 turtle sudo[4110437]: pam_unix(sudo:session): session opened for user moriwaka(uid=1000) by (uid=0) ここでpstreeを実行すると、プロセスの親子関係としてはsystemd→hoge.sh→sudo→sleep のように意図どおりであることがわかります。 # pstree systemd─┬─ModemManager───3*[{ModemManager}] ├─NetworkManager───2*[{NetworkManager}] (中略) ├─hoge.sh───sudo───sleep (以下略) 別のcgroupだと何がまずいのか? 起動に成功しているが、cgroupが別であることはわかりました。 これで問題が発生するのはどんなときでしょうか? systemdのhoge.serviceがプロセスの監視をできていない。systemdがサービスの名前からサービス本体のプロセスを発見できなくなります。そのため systemctl kill や、(この例にはありませんが)Restartディレクティブなどを使おうとするとうまくいきません。daemonizeするプログラムだと、bashを停止しても動作しつづけますから管理ができなくなります。 ユーザセッション(session-c4.scope)とサービス(hoge.service)の間に前後関係などは定義されません。システム全体のシャットダウン時には(この例では定義していませんが)サービス終了コマンドを用意してもそれを使わずにデフォルトの終了方法(SIGTERM送信のあとタイムアウトまちしてからSIGKILL送信)で終了される場合があります。 service unitでリソース設定や権限管理を行っていても、その制限がうまく反映されない場合があります。 関連するログがユーザセッションに所属するのでjournalをサービスで検索すると見逃す場合があります。 対策はsetprivコマンド sudoはセッションを作るのでまずいことがわかりました。 このようなシェルスクリプトでsudoが利用されるのは典型的にはrootからサービス用ユーザへの切り替えのためですから、 UID, GIDの切り替えだけを行いセッションについては何もしない(PAMをつかわない)しくみがあると都合がいいです。 まさにそのようなコマンドがsetprivコマンドです。setprivコマンドはutil-linuxパッケージに含まれています。 さっそく /opt/hoge/hoge.sh のsudo をsetprivに置き換えます /opt/hoge/hoge.sh #!/bin/bash setpriv --reuid=1000 --regid=1000 --init-groups sleep 5000 今動いているsleepを止めます。(この例だとdaemonizeしていないので、systemctl stopでbashへシグナルを送ることでsleepを終了できます。) # systemctl stop hoge.service 再度 hoge.service を起動してみます。今度は cgroup /system.slice/hoge.service の中でsleepが実行されていることがわかります。 # systemctl start --no-block hoge.service # systemctl status hoge.service ● hoge.service - hoge Loaded: loaded (/etc/systemd/system/hoge.service; static) Active: activating (start) since Wed 2022-09-21 14:38:47 JST; 9s ago Main PID: 4115616 (hoge.sh) Tasks: 2 (limit: 37738) Memory: 540.0K CPU: 5ms CGroup: /system.slice/hoge.service ├─ 4115616 /bin/bash /opt/hoge/hoge.sh └─ 4115617 sleep 5000 Sep 21 14:38:47 turtle systemd[1]: Starting hoge.service - hoge... 期待どおりUID, GIDが変更されていることを確認します。 # cat /proc/4115617/status |grep [UG]id Uid: 1000 1000 1000 1000 Gid: 1000 1000 1000 1000 このようにsetprivを利用してUID, GIDを変更するとセッションを作成しないので、systemdによるサービス管理の仕組みと競合しません。
こんにちは、ソリューションアーキテクトの蒸野(ムシノ)です。 今回は、Red Hat Developerのブログ Get started with Red Hat OpenShift Connectors | Red Hat Developer の翻訳記事を紹介させて頂きたいと思います。 一部、記事の内容を分かりやすくするため、オリジナルにない画面表示や補足事項を追加しています。ご承知おきいただけらと思います。 はじめに 「Red Hat OpenShift Connectors」は、Red Hat が提供する新しいクラウドサービスです。 システム間で迅速かつ信頼性の高い接続を実現するためのコネクタを提供するサービスであり、これらコネクタはフルマネージドで提供され、Apache Kafka向けのマネージドクラウドサービス である Red Hat OpenShift Streams for Apache Kafka と密接に統合されています。 Red Hat OpenShift Connectors は、現時点ではサービスプレビュとなっています。 サービスプレビュープログラムの一環とし、現在最大4つのコネクタをデプロイできますが、48時間後にはクリアされます。 コネクタにはソースコネクタとシンクコネクタがあり、ソースコネクタは外部システムから OpenShift Streams for Apache Kafka にデータを送信し、シンクコネクタは OpenShift Streams for Apache Kafka から外部システムへデータを送信することが可能です。現時点では50以上のコネクタを提供しており、これらには Camel K に基づく様々なクラウドサービスへのコネクタ、および、チェンジデータキャプチャの Debezium プロジェクトに基づくデータベース用のソースコネクタが含まれています。 利用可能なコネクタについては、下記で詳細を確認することができます。 www.redhat.com それでは、OpenShift Connectors の基本的な使い方を紹介していきたいと思います。 前提環境 利用環境として、OpenShift Streams for Apache Kafka の利用を前提としており、事前にKafkaインスタンスの作成を行っておく必要があります。 また、Kafkaインスタンスのステータスが「Ready」状態であることも必要となっています。 例えば、下記のような Kafkaインスタンス を事前に用意下さい。 Kafkaインスタンスを作成するための手順については、下記を参照してください。 developers.redhat.com または、こちらの記事も是非ご参照ください。 rheb.hatenablog.com OpenShift Streams for Apache Kafkaの設定 Red Hat OpenShift Connectors で使用するため、OpenShift Streams for Apache Kafka にはいくつかの設定が必要となります。 まず、 OpenShift Streams for Apache Kafka には、データソース側 が送信したメッセージを コネクタ が利用できるようにするための Kafkaトピック の作成が必要です。次に Kafka インスタンスとコネクタを接続して認証するためのサービスアカウントの作成、および サービスアカウントに対するアクセス権限の設定(コネクタによる Kafka トピックへのアクセスと使用方法の定義)が必要となります。 では、 OpenShift Streams for Apache Kafka の設定を行っていきましょう。 1. Kafkaトピックを作成する まずは、Kafkaトピックを以下を参考に作成します。 Red Hat アカウントで、 https://console.redhat.com のハイブリッドクラウドコンソールにログインします。 「Application and Data Services」>「Streams for Apache Kafka」>「Kafka instances」 に移動し、前提環境で作成した Kafka インスタンスを選択します。 「Topic」タブを選択し、「Create Topic」をクリックします。 トピックに名前(例:test-topic)を入力します。パーティション数やメッセージ保持、およびレプリカについては任意に設定ください。 2. サービスアカウントの作成 次に、OpenShift Streams for Apache Kafka にアクセスするためにのアカウントが必要です。以下のようにサービスアカウントを作成します。 ハイブリッドクラウドコンソールで、「Service Accounts」を選択し、「Create a service account」をクリックします。 サービスアカウントに名前を入力し、「作成」をクリックします。 「Client ID」と「Client Secret」が作成されます。これらの認証情報はコネクタを構成する際に使用しますので、安全なところに保存しておきます。 サービスアカウント作成後「I have copied the client ID and secret」チェックボックスを選択し、「Close」 をクリックします。 3.サービスアカウントのアクセスレベルを設定する サービスアカウントが作成できたら、次は必要なパーミッションを設定します。 今回の例ではサービスのコンシューマと別のサービスのプロデューサになる必要がありますので、両方のパーミッションを有効にセットします。 アクセスレベルを次のように設定します。 ハイブリッドクラウドコンソールで、「Streams for Apache Kafka」>「Kafka instances」を選択します。前回同様、作成済みのKafkaインスタンスを選択します。 「Access」タブをクリックします。Kafkaインスタンスの現在のアクセス制御リスト(ACL)が表示されますので、[Manage access]をクリックします。 「アカウント」を選択するメニューが表示されるので、作成済みのサービスアカウントを選択し、[次へ]をクリックします。 「Assign Permissions」で、「Add permission」をクリックします。 「Add permission」のドロップダウンメニューから、[Consume from a topic] を選択します。すべてのリソース識別子の条件を is にし、すべての識別子の値を * に設定します。 「Add permission」のドロップダウンメニューから、[Produce to a topic] を選択します。すべてのリソース識別子の条件を is にし、すべての識別子の値を * に設定します。 パーミッション追加の設定画面は下記のようになります。 最終的にパーミッション設定後、次のようになっていれば問題ありません。 これで OpenShift Streams for Apache Kafka が、コネクタで使用できるようになりました。 ソースコネクタの作成 ソースコネクタは、外部データソースから Kafkaメッセージ を生成します。今回は、「Data Generator」 ソース コネクタを使用したいと思います。 このコネクタは、実際には外部システムからデータを取得しませんが、一定間隔でトピックにKafkaメッセージを通知します。 次のようにコネクタを作成します。 1.ハイブリットクラウドコンソールで、[Connectors]を選択し、[Create a Connectors Instance]をクリックします。 2.データソースとして使用するコネクタを選択します。利用可能なコネクタのカタログを参照するか、コネクタを名前で検索し、シンクまたはソースコネクタを見つけることができます。たとえば、「Data Generator」 ソース コネクタの場合には、検索ボックスに data と入力します。そうすると、「Data Generator」 コネクタのみが表示されます。「Data Generator」をクリックしてコネクタを選択、「Next」 をクリックします。 3.次に、先程コネクタ用に設定した OpenShift Streams for Apache Kafka インスタンスを選択し、「Next」をクリックします。 4.「Namespace」では「Create preview namespace」をクリックして、コネクタを所属させるための Namespace を作成します。 この Namespace は48 時間経つと削除されます。また、1 つのネームスペースにつき、最大 4 つのコネクタを作成することができます。 では、Namespace が作成できたら該当 Namespace を選択し、Next をクリックします。 5.コネクタのコンフィギュレーションを以下のように設定します。 5.1.コネクタの名前を指定します。 また、コネクタ用に作成したサービスアカウントの「Client ID」と「Client Secret」を入力し、Next をクリックします。 5.2.コネクタ固有の設定値をセットします。 データ形式フォーマット:デフォルトのapplication/octet-streamとします。 トピック名:コネクタ用に以前に作成したトピック名(例:test-topic)を入力します。 コンテンツタイプ:デフォルトの text/plain とします。 メッセージ:コネクタがKafkaトピックに送信するメッセージの内容を入力します。ここでは、「Hello World!」と入力します。 実行間隔:コネクタがKafkaトピックにメッセージを送信する間隔(ミリ秒単位)を指定します。今回は10秒ごとにメッセージを送信するので「10000」と設定します。 5.3.コネクタインスタンスのエラー処理ポリシーを設定します。 デフォルトは「stop」で、エラーが発生するとコネクタはシャットダウンします。 今回は、標準のまま「stop」を選択します。 5.4.設定内容のレビュー 最後に設定内容をもう一度確認し、「Create Connector」を行います。 コネクタインスタンスが表示され、コネクタの状態が表示されると思います。 数秒後、コネクタインスタンスが作成され、ステータスが Ready 状態に変わります。これでソースコネクタが作成できました。 ちなみにコネクタの停止、起動、削除する場合には、コネクタのオプションアイコンをクリックして操作を行うことができます。 5.5.Kafka Topicを確認する 既に、コネクタによってKafka トピックへの送信が開始されています。 Kafkaトピックの「message」タブをからメッセージの送信状況が確認できます。 シンクコネクタの作成 シンクコネクタは、Kafkaトピックからメッセージを取得して外部システムにメッセージを送信します。 今回は「HTTP Sink」コネクタを使用します。 HTTP Sink コネクタは、Kafkaトピックから Kafka メッセージを取得し、HTTP エンドポイントにメッセージを送信します。 Webhook.site サービスは、簡単にHTTP エンドポイントを取得するサービスを提供します。 今回は、これを使ってHTTPシンクコネクタからデータを送付します。 まずは、 https://webhook.site に移動します。 このWebhookサービスのページにアクセスすると、下記のようにデータシンクとして使用できる URL が発行されます。 URL を取得したら、次のようにエンドポイントを設定します。 1.「Connectors」 を選択し、「Create Connectors instance」をクリックします。 2.HTTP Sink コネクタを見つけるには、検索フィールドに http と入力します。 3.HTTP Sink コネクタをクリックし、「Next]」をクリックします。 4.コネクタが連携する OpenShift Streams for Apache Kafka インスタンスを選択します。 5.「Namespace」ページで、ソースコネクタを作成したときに作成した Namespace を選択し、「Next」 をクリックします。 6.コネクタのコンフィギュレーションを以下のように設定します。 6.1. コネクタの名前を入力します。また、コネクタ用に作成したサービスアカウントの「Client ID」と「Client Secret」を入力し、[Next] をクリックします。 6.2. コネクタ固有の設定値をセットします。 HTTPシンクコネクタの場合、以下の情報を提供します。 データ形式フォーマット:デフォルトのapplication/octet-streamとします。 メソッド:デフォルトのPOSTのままにします。 URL:Webhook.siteから取得したURLを入力します。 トピック名:ソースコネクタに使用したトピック名前(例:test-topic)を入力します。 6.3. エラー処理ポリシーを停止に設定します。 6.4. 設定内容のレビュー コネクタの設定値をもう一度確認し、[Create Connector] をクリックします。 コネクタインスタンスが表示され、数秒後コネクタインスタンスのステータスが Ready 状態に変わります。 コネクタはKafkaトピックからメッセージを取得し、HTTPエンドポイントに送信します。 Webhook.siteのサービス上 から取得した固有の URL にアクセスすると、 ソースコネクタで定義した「Hello World!」メッセージが10秒間隔で表示されていることが分かります。 これで初めての Red Hat OpenShift Connector インスタンスを作成し OpenShift Streams for Apache Kafka にメッセージの送信と取得することが確認できました。 Red Hat OpenShift Connectors は外部システムやクラウドサービスへコネクタを使って連携することができます。是非とも他のコネクタを作成して機能を試してみて下さい。 次の記事では、Red Hat OpenShift Connector を使用してクラウドベースのデータベースに接続し、そのデータベースからデータ変更イベントを取得する方法を紹介します。 最後に 如何でしたでしょうか? 基本的な Red Hat OpenShift Connectors の利用でしたが、始め方が分かったのではないでしょうか? また、サービスプレビュ版のため、一部の制限が掛かっていますが、便利なことは実感いただけたのではないでしょうか。 是非一度ご利用いただくことをお勧めいたします。 直近のイベントのご案内 www.redhat.com
TL;DR ファイルを見る 「Build ID」とは 「debuginfo」 とは debuginfoとBuild IDの関係 「debuginfoの素早い検証」 「ダンプに対応するバイナリの発見」 複数バージョンのdebuginfoをシステムへ導入する /usr/lib/.build-id の通常rpmへの移動 Build IDの応用 Red Hatの森若です。 rpmパッケージに含まれるファイル一覧を rpm -ql glibc のようにして表示すると、/usr/lib/.build-id ディレクトリ以下に多数のファイルがあることに気付きます。 「このファイルは何ですか?」 という素朴な疑問に答えるため、背景知識をふくめて紹介していきます。 $ rpm -ql glibc (中略) /lib64/librt.so.1 /lib64/libthread_db.so.1 /lib64/libutil.so.1 /sbin/ldconfig /usr/lib/.build-id /usr/lib/.build-id/03 /usr/lib/.build-id/03/f4fad34e4c1a28712ec68f3c106fc200e25c60 /usr/lib/.build-id/06 /usr/lib/.build-id/06/3f74dd93e6bd487beebd93975dd7d6a75e7c4d /usr/lib/.build-id/06/f56ed55884abd425ff0fd12872bf3fae0793b8 /usr/lib/.build-id/0f /usr/lib/.build-id/0f/90095799730a30406eb56af85830689679b356 /usr/lib/.build-id/1e /usr/lib/.build-id/1e/acb7c50f7ed20ef1fefda3aa9c67377686acf5 /usr/lib/.build-id/22 /usr/lib/.build-id/22/f2295089fd568f1ec1ff6f40c51b3ef4dd528a /usr/lib/.build-id/31 /usr/lib/.build-id/31/9dab886fc7c7c8bc15b4aa33ed5b08fb84a579 (以下略) TL;DR /usr/lib/.build-id にあるシンボリックリンクはELFファイル(実行ファイルや共有ライブラリ、カーネルモジュール)に含まれる Build IDに対応している Build IDはELFファイルに格納されているIDで、内容のハッシュ値。 Build IDの用途は大きくわけて2つ。/usr/lib/.build-idは後者のために利用されている。 デバッグに必要な情報を提供する debuginfo パッケージ内のファイルと実行用のELFファイルが対応していることの確認 コアダンプに保存したBuild IDからELFファイルを検索 RHEL 7までは /usr/lib/.build-id に相当するファイルは debuginfo パッケージに含まれていた RHEL 8以降は、複数バージョンのdebuginfoパッケージをインストールできるように通常のrpmにbuild-idが含まれるようになった 複数バージョンのdebuginfoパッケージをインストールしたい主なシチュエーションはコンテナ利用時 ファイルを見る まずはファイル /usr/lib/.build-id/03/f4fad34e4c1a28712ec68f3c106fc200e25c60 を対象として少し調べてみましょう。 $ ls -l /usr/lib/.build-id/03/f4fad34e4c1a28712ec68f3c106fc200e25c60 lrwxrwxrwx. 1 root root 33 Sep 7 03:35 /usr/lib/.build-id/03/f4fad34e4c1a28712ec68f3c106fc200e25c60 -> ../../../../lib64/libnss_dns.so.2 → ライブラリへのシンボリックリンクになっています fileコマンドで見てみます。 $ file /lib64/libnss_dns.so.2 /lib64/libnss_dns.so.2: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=03f4fad34e4c1a28712ec68f3c106fc200e25c60, for GNU/Linux 3.2.0, stripped → "BuildID[sha1]=03f4fad34e4c1a28712ec68f3c106fc200e25c60" これがファイル名の 03/f4fad34e4c1a28712ec68f3c106fc200e25c60 に対応しています! ライブラリに含まれている「Build ID」というものに対応していることがわかります。ディレクトリ名からも正しそうですね。 「Build ID」とは Build IDは Fedora 8から導入された比較的古い機能で、FedoraやRHELでELF形式の実行ファイルや共有ライブラリ、カーネルモジュールに付与されているユニークなIDです。 FedoraやRHELではファイルの内容のSHA1ハッシュ値を使って生成しています。そのため内容が完全に同じであれば同一ですが、そうでなければ別のBuild IDが付与されます。 Fedora Wikiを見るとFedoraにBuild IDを導入した時の機能ページがあります。 fedoraproject.org どのような課題を解決するための機能であるかという「The Problems」を見ると、debuginfoの素早い検証(Quick debuginfo validation)と、ダンプに対応するバイナリの発見(Finding binaries for dumps)の2項目が記載されています。 「debuginfo」 とは RHELやFedoraでC言語などでのアプリケーションの開発・デバッグを行なっていない方には「debuginfo」は耳慣れない言葉かと思います。 debuginfo パッケージはrpmパッケージの一種です。C、C++言語などのソースレベルデバッグに使うデバッグ情報とソースコードを提供しています。FedoraとRHELのELFバイナリを含むrpmには、それぞれ対応するdebuginfoパッケージが提供されています(debugsource パッケージもありますがここではまとめて「debuginfoパッケージ」と呼びます。)。現在実行中のプログラムについてデバッグをしたい場合には、実行中のプログラムを含むrpmに対応するdebuginfoパッケージをインストールすることで、対応するソースコードを確認しながらデバッグができます。 実際にdebuginfoをインストールしてデバッグ作業につかうには、dnfのdebuginfo-installコマンドでもとのパッケージ名を指定してインストールします。 $ sudo dnf debuginfo-install glibc (中略) Dependencies resolved. ================================================================================================================ Package Architecture Version Repository Size ================================================================================================================ Installing: glibc-debuginfo x86_64 2.35-17.fc36 updates-debuginfo 4.5 M glibc-debugsource x86_64 2.35-17.fc36 updates-debuginfo 5.3 M Transaction Summary ================================================================================================================ Install 2 Packages Total download size: 9.8 M Installed size: 54 M Is this ok [y/N]: debuginfoパッケージがデバッグに利用するものだとわかりました。 debuginfoとBuild IDの関係 debuginfoパッケージとBuild IDとの関係をみていきます。 C言語等で典型的にはソースコードから、コンパイル、リンクという処理を経てELFファイル(実行ファイルや共有ライブラリなど)を作成します。 機械語とソースコードの対応関係や、メモリ上のデータと変数の対応関係などがデバッグ情報として機械語とともに生成され、ELFファイルに格納されます。 Build IDはリンク時に生成します。 FedoraやRHELでは、その後実行に必要な部分だけを含むELFファイルと、デバッグ時に必要な情報だけを含むdebuginfoパッケージ用のELFファイルを生成します。 リンク時にBuild IDが生成される debuginfoパッケージを別のパッケージに分離することは技術上必須の作業ではありません。しかしサイズがかなり大きい(たとえば glibc パッケージは1.9MBに対して glibc-debuginfoとglibc-debugsource をあわせると9.8MB)ため、分離して配布されています。 「debuginfoの素早い検証」 実行に使っているrpmパッケージとdebuginfoパッケージのバージョンがずれてしまうことがあります。異なる内容のdebuginfoは間違った地図のようなもので、デバッグの役に立ちません。ここでBuild IDが活躍します。 実行ファイルのBuild IDと、debuginfoパッケージのELFファイルのBuild IDを対照して同じである場合にだけ利用します。 rpmパッケージのバージョンが対応することを利用しても確認できますが、この仕組みの方が簡単に行えます。(rpmのバージョン比較ではうまく処理できないケースにも対応できます。) 「ダンプに対応するバイナリの発見」 linuxはアプリケーションの異常時に「coredump」とよばれるプログラムのメモリの状態を出力します。このときに、利用している各ELFファイルのBuild IDを保存することで、新旧ふくめたどのELFファイルであるかを発見できるようになるというものです。Build ID登場以前には、ファイル名しか保存されていなかったため調査が難しくなることがありました。 /usr/lib/.build-id はこの Build IDからELFファイルへの逆引きのために利用されています。 以下はFedora 36で sleep コマンドへSIGQUITを送って出力させた coredump の情報です(注: systemd-coredumpによる情報収集によりcoreに含まれない情報も表示されています)。 build-id という欄で /usr/bin/sleep のBuild IDがわかり、"Module xxx with build-id xxxxxxxxxxxxx" のような行でライブラリのBuild IDもわかります。 PID: 36828 (sleep) UID: 11956 (kmoriwak) GID: 11956 (kmoriwak) Signal: 3 (QUIT) Timestamp: Wed 2022-09-14 17:13:06 JST (14s ago) Command Line: sleep 4 Executable: /usr/bin/sleep Control Group: /user.slice/user-11956.slice/user@11956.service/app.slice/app-org.gnome.Terminal.slice/vte-spawn-86bbb4b6-93e6-42cc-ae07-043f95a3625f.scope Unit: user@11956.service User Unit: vte-spawn-86bbb4b6-93e6-42cc-ae07-043f95a3625f.scope Slice: user-11956.slice Owner UID: 11956 (kmoriwak) Boot ID: 91c9b907c35f4893bb93d164afcaca94 Machine ID: b6024ba9744641fdb43d59bec9a81a4d Hostname: kmoriwak.usersys.redhat.com Storage: /var/lib/systemd/coredump/core.sleep.11956.91c9b907c35f4893bb93d164afcaca94.36828.1663143186000000.zst (present) Disk Size: 20.5K Package: coreutils/9.0-8.fc36 build-id: d5198b0c63efcb64e548b22d75f0349f8cac8e30 Message: Process 36828 (sleep) of user 11956 dumped core. Module linux-vdso.so.1 with build-id ef533bb262f1a045f813981c3d60075160f0756a Module ld-linux-x86-64.so.2 with build-id 1eacb7c50f7ed20ef1fefda3aa9c67377686acf5 Module libc.so.6 with build-id 9c5863396a11aab52ae8918ae01a362cefa855fe Module sleep with build-id d5198b0c63efcb64e548b22d75f0349f8cac8e30 Metadata for module sleep owned by FDO found: { (以下略) 複数バージョンのdebuginfoをシステムへ導入する 数年前(Fedora 26, RHEL 7)まで、複数バージョンのdebuginfoをシステムに同時にインストールすることはできませんでした。 実際にはシステムで同じパッケージの複数バージョンが動作しますので、これにあわせて複数バージョンのdebuginfoをインストールできるように 仕組みが改善されました。冒頭の /usr/lib/.build-id は、このタイミングで導入されています。 fedoraproject.org 同じソフトウェアの複数バージョンが動作するのはどんな時でしょう。 コンテナを利用している i686とx86_64 のような複数アーキテクチャのプログラムが同時に利用されている パッケージを更新したが古い実行ファイルで起動したプロセスの実行が続いている さまざまなケースがありますが、OCIコンテナが普及して、コンテナイメージとホストOSの更新タイミングが一致しないケースが増えています。 そのような場合でもデバッグが容易になるよう、複数バージョンのdebuginfoをインストールできるようにする必要がありました。 /usr/lib/.build-id の通常rpmへの移動 実行用のrpmが/usr/lib/.build-idを提供するようになった 以前のdebuginfoパッケージには、対応する実行用のrpmパッケージ内の実行ファイルや共有ライブラリ等へのシンボリックリンクが /usr/lib/debug/.build-id/ 以下に 含まれていました。もしも複数バージョンのdebuginfoをインストールできるように制限だけをゆるくすると、実行ファイル等と実際には対応しないシンボリックリンクが多数作成され、coredumpに含まれるBuild IDからバイナリを発見することが難しくなります。 そのため、Build IDから実行ファイルを発見するためのシンボリックリンクを実行用のrpmパッケージが持つように変更されました。 Build IDの応用 Fedora 31以降やRHEL 8.5以降、RHEL 9.0以降では、この記事で登場したBuild IDを応用した debuginfod を利用できます。詳しくは下の記事をご覧ください。 rheb.hatenablog.com
こんにちは、Red Hatでソリューションアーキテクトをしている北村です。 みなさん、OpenShiftのアップグレードしていますか?Kubernetesを運用する上で避けて通れないのがクラスターのライフサイクル管理です。Kubernetesは従来のプラットフォームよりもバージョンアップの頻度が多くサポート期間が短いので、定期的なバージョンアップ対応が必要になります。そしてそれはKubernetesのディストリビューションであるOpenShiftも同様です。 しかしクラスターアップグレードを実行する際、インフラの構成やアプリケーションの作り、マニフェストの設定などによってサービス断が発生する可能性があります。そのためサービスの可用性を最優先するためのアップグレード戦略として複数クラスターによるBlue/Greenアップグレードを採用するケースがあります。 とはいえBlue/Green構成を組むのも一苦労な上、クラスターの切り替えも色々な方法があるので、どの方法を採用するかを検討するのも大変です。 そこで今回は、このBlue/GreenアップグレードをAWS上のOpenShiftで実現する際のクラスター切り替え方式についてまとめてみました。 今回取り扱う切り替え方式 今回は以下の4つの方式についてまとめました。 DNS切り替え Cloud Front切り替え HAProxy切り替え Global Accelerator切り替え DNS 切り替え Blue/Greenの切り替えで一番メジャーな方法がこのDNS切り替えだと思います。対象のレコードのルーティング先を片方のクラスターに向け、サービスアウトした上でアップグレードを実行します。 AWSのRoute 53を利用する場合、加重ルーティングを活用するとレコードの追加・削除をすることなく、重量のみを変更して通信の切り替えを実現できます。サービスアウトしたい方のレコードの重量を0に変更することで、1以上の重量を設定したレコードのみ利用されます。両方0にすると同じ流量でルーティングされますが、このように通信切り替えを行うことを想定して、通常は1以上の重量を設定しておきます。 この手法での注意点はやはりTTLの管理になります。レコードの伝播もさることながら、このTTLが切れるまでは切り替えは発生しないため、そのタイムラグを許容できるかが採用のポイントになります。なおRoute 53ではCLBの宛先をAliasレコードとして登録することができますが、AliasレコードのTTLはルーティング先のサービスの仕様に依存します。CLBの場合はTTLが60秒となるため、これより短くしたい場合はCNAMEでの登録を検討ください。 DNS切り替えは設定箇所も少なく、プライベートホストゾーンを使うことで非インターネット環境でも利用可能なため、多くの環境でこの手法を採用することが可能です。Blue/Greenを実装する場合、まずはこの方式から検討することをおすすめします。 CloudFront 切り替え AWSのマネージドCDNサービスであるCloudFrontを利用して切り替えることもできます。 Route 53でドメインの宛先をCloud Frontに向けつつ、CloudFront側の設定変更でクラスターへの通信を制御します。この際キャッシュが効いているとキャッシュのTTL分だけ切り替えが行われないため、切り替えを素早く実施する場合はCloudFrontのキャッシュのTTLを0秒に設定しておきましょう。 切り替え時間についてはDNSより早く切り替えが実現できる印象がありますが、この点はCloudFrontの設定変更の伝播に依存するため、自分の環境で試してみた方が良いかと思います。ちなみに私が試した際はおおむね20秒前後で切り替えができました。 この手法の注意点は、インターネット公開環境しか利用できないことと、通常時にAct/Actの構成を組めないことになります。ビヘイビアに複数のオリジンを指定することは可能ですが、優先順位が設定されるためラウンドロビンのような通信の分散はできません。DNSのTTLの考慮が面倒くさかったり、レコードの設定変更が権限上難しい場合に検討いただくと良いかと思います。 HAProxy 切り替え 自前のEC2インスタンス上にHAProxyを構築して、Configによる切り替えを実現します。 この手法の強みは、HAProxyによる柔軟なルーティング設定/ログ設定が可能なことと、通信の即時切り替えが可能な点です。今回紹介する手法の中で唯一、通信の即時切り替えができるため、アプリの要件としては採用する可能性が高い手法になります。 ただし他の手法とは異なり、構成するコンポーネントがマネージドサービスでないため、障害への対策やパフォーマンスのチューニングをきちんと設計する必要があります。下の図ではHAProxy 環境を単一のインスタンスで表現していますが、複数AZへの配備やAutoscaling Groupの利用、インスタンスの前段にさらにELBを設置するなど、本番環境として構成する場合は検討項目が多いことにご注意ください。 Global Accelerator 切り替え かなりマイナーな選択肢になりますが、Global Acceleratorを利用してクラスターの切り替えを実現することも可能です。クラスターのエンドポイントをGlobal Acceleratorリスナーのエンドポイントグループに登録し、”重み”の設定を変更することでクラスター切り替えを実現します。 この手法はGlobal Acceleratorの仕様上CLBをエンドポイントに指定できないため、Service(Type: LoadBalancer)などで別途NLBを立てる必要があります。この場合はRouter独自の機能(TLS終端など)が使えないため、個別の設定考慮が必要です。Service個々にURLを設定するため、ワイルドカードドメインの利用もできなくなります。 切り替えについてはエンドポイントごとに設定する”重み”をコントロールして実現します。Route 53の加重ルーティングと同様、サービスアウトしたいエンドポイントの”重み”を0にすることで片方のクラスターのみに通信を流すことができます。この手法はCloudFront同様、インターネット公開環境のみでの利用、かつグローバルのエッジロケーションに設定が伝播するまでタイムラグがあるため、これらの特性を理解した上で採用を検討してください。 ぱっと見ると”CloudFront切り替え”の劣化版のように見えますが、この手法では通常時にクラスターのAct/Act構成を取ることができます。そのため、Route 53での切り替えが困難だけど、Act/Act構成をとっておきたいといった要件があった場合はこの手法を選択肢のひとつとして検討していただければと思います。 まとめ ざっとまとめると以下のような表になります。個人的なおすすめ度は左から高い順になっています。 今回はAWS環境でOpenShiftを運用している際のクラスター切り替え方式にフォーカスしましたが、クラウドサービスごとにサービス仕様(特にネットワーク周り)が微妙に異なるので、クラウドの特性やアプリケーション/インフラの要件を加味して最適な方式を模索いただければと思います。