YAMAGUCHI::weblog

海水パンツとゴーグルで、巨万の富を築きました。カリブの怪物、フリーアルバイター瞳です。

OpenTelemetryでgRPCのヘルスチェックのトレースを無視する

はじめに

OpenTelemetryを使ってgRPCのトレースを楽に取ろうと思うと otelgrpc を使ってよしなにリクエストのトレースを取っていることと思います。

たとえばサーバー側であれば

interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider())
srv := grpc.NewServer(
        grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor(interceptorOpt)),
        grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor(interceptorOpt)),
    )

クライアント側であれば

interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider())
*conn, err = grpc.DialContext(ctx, addr,
    grpc.WithTransportCredentials(insecure.NewCredentials()),
    grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor(interceptorOpt)),
    grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor(interceptorOpt)),
)

といった具合にOpenTelemetry用のインターセプターを挿入することになると思います。(あえて明示的にTracerProviderを指定していますが、globalなものを使うのであればこれは必要ありません)

しかし、これをこのまま実行すると、KubernetesなどでgRPCのヘルスチェックをしているようなときに、そのトレースまで送られてしまいます。つぎのスクリーンショットで大量の grpc.health.v1.Health/Check のトレースが見えています。(分布図の最下部あるトレースがそれ。スクリーンショットは1つだけハイライトしたもの。)

実際にこれが鬱陶しいのでフィルターをOpenTelemetry側でしてほしいという要望がいくつか出ています。(1つは私が起票したものですが...)

これは当然で、単純に無駄なトレースが大量に表示されてダッシュボードが見づらくなるだけでなく、従量課金になっているようなサービスではコストの無駄になります。そこでワークアラウンドが欲しくなります。

WithSamplerワークアラウンドする

特定のトレースを取得するかどうかをAPIで差し込める余地は実は殆ど無く、あるとしたら

  • 自らexporterを書いて特定のトレースはバックエンドに送らないようにする(あるいはexporterによってはそのようなオプションがあるかも)
  • Samplerを自分で書く

くらいしかありません。前者は実現したい事柄に対してのワークアラウンドが大きすぎるので非現実的です。後者は多少コードは多くなるけれど、まだ許容できる範囲なので、こちらでやっていこうと思います。

import sdktrace "go.opentelemetry.io/otel/sdk/trace"

func IgnoreWithNameSampler(targets []string) sdktrace.Sampler {
    return ignoreWithNameSampler{
        targets: targets,
    }
}

type ignoreWithNameSampler struct {
    targets []string
}

func (s ignoreWithNameSampler) Description() string {
    return "drop all spans with the name that contains one of targets."
}

func (s ignoreWithNameSampler) ShouldSample(p sdktrace.SamplingParameters) sdktrace.SamplingResult {
    ts := trace.SpanContextFromContext(p.ParentContext).TraceState()
    for _, t := range s.targets {
        if strings.Contains(p.Name, t) {
            return sdktrace.SamplingResult{
                Decision:   sdktrace.Drop,
                Tracestate: ts,
            }
        }
    }
    return sdktrace.SamplingResult{
        Decision:   sdktrace.RecordAndSample,
        Tracestate: ts,
    }
}

これを作ったらあとは普通にTraceProviderにSamplerとして指定するだけです。

tp := sdktrace.NewTracerProvider(
    sdktrace.WithSampler(IgnoreWithNameSampler([]string{"grpc.health.v1.Health/Check"})),
    sdktrace.WithBatcher(exporter),
)

紹介してきましたが、本来であれば TraceIDRatioBasedなどと組み合わせて簡単にやりたいはずなので、標準で持っていてくれていいはずなのにとは思います。

追記(2022.07.12 14:30)

レポートと共にPull Requestを出した。

github.com

追記(2022.08.17 16:30)

Pull Requestが無事にマージされたのでこれからは go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters を使えば簡単にフィルターできます。

github.com

OpenTelemetryでSpanのレイテンシーを取得する

はじめに

分散トレースでリクエスト全体の中でのボトルネックを発見すると同時に、ユーザーに対するSLOとしてトレース全体のレイテンシーを取得することもあると思います。その場合OpenTelemetryではすんなりとルートSpanのレイテンシーを取得できないため、その方法をメモしておきます。

バージョン

通常の計装

まずこういう典型的な計装があったとします。

func sleepHandler(w http.ResponseWriter, r *http.Request) {
    tracer := otel.Tracer("handler.sleep")
    ctx := context.Background()
    ctx, span := tracer.Start(ctx, "request.sleep")
    defer span.End()
    ...(なにかする)...
}

この span にかかったレイテンシーを取得したいとします。しかし go.opentelemetry.io/otel/trace.Span には開始時刻と終了時刻のタイムスタンプを取得するメソッドが定義されていません。

しかし直感でも分かる通り、また仕様でも定義されている通り、Spanはその開始時刻と終了時刻を保持しているので、アクセスするためのインターフェースがどこかにあるはずです。

github.com

tracesdk.ReadOnlySpanに変換する

で、よくコードを読んでみると go.opentelemetry.io/otel/sdk/trace.ReadOnlySpan にはそのためのインターフェースが定義されています。

pkg.go.dev

したがって、上記計装から次のようにすれば spanレイテンシーを取得できます。

func sleepHandler(w http.ResponseWriter, r *http.Request) {
    tracer := otel.Tracer("handler.sleep")
    ctx := context.Background()
    ctx, span := tracer.Start(ctx, "request.sleep")
    ...(なにかする)...
    span.End()
    ro := span.(tracesdk.ReadOnlySpan)
    start, end := ro.StartTime(), ro.EndTime()
    duration := end - start
    ...(durationをメトリクスとしてバックエンドに送る)...
}

注意することとしては span.End() を先に呼んでいないとspanのendtimeが打刻されないので、defer span.End() にしていた部分は何かしら書き換えないといけないということです。

他の言語でも同様

上記の例はすべてGoで書いていたけれど、OpenTelemetryは仕様が全言語でインターフェースレベルで共通で実装されているので、おおよそこういった実装は他の言語でも共通になっています。

2022.06.15 追記

OpenTelemetryの仕様 によると、No-op Tracerの場合はそもそもReadOnlySpanにキャストできない様子なので、その場合の対応は考える必要がありそうです。

SimplerのSliceで切り取った音をMIDIノートに割り振って鳴らす

やりたかったこと

サンプリング音源(aac、mp3など)を更に切り取ってMIDIのノートに割り当て、アレンジした形で鳴らす

手順

まず Instruments > Simpler を選択して、適当にトラックにドラッグ・アンド・ドロップする。

すると画面下部のクリップビューにサンプルを指定するようにメッセージがでるので、サンプルをドラッグ・アンド・ドロップして指定する。

するとサンプルの波形が出てくるので、Sliceのボタンを押す。

このとき Sensitivity がデフォルトだと100%になっていて、切られすぎてしまうので感度を下げてあげるといい感じに切れるところが出てくる。またPlaybackはデフォルトだと Mono になっていて、この場合ある瞬間に1つのノートしか鳴らせないので、複数の音を出したい場合には Poly にしておく。Poly に変更すると Voices という項目が増えるが、これは同時に鳴らせる音の最大数。

ここでスライスは水色の線で切られるが、もし切り取り位置が気に入らなければ、水色の線の上部にある下三角を移動させれば切り取り位置が変更できる。

これで鳴らす準備ができたので、次にどのノートがどの音に割り当てられてるか簡単に確認する。画面を見て左から数えていってもいいのだけれども、鍵盤を押して確認するほうが楽なのでその方法で行う。MIDIコンがある場合はまた別だけども、とりあえずPCキーボードのまま確認する場合は、画面右上の鍵盤アイコンをクリックする。

また録音モードにしておかないと自由に鍵盤を鳴らせないのでSimplerを載せているトラックの右側にある音符アイコンを赤くなるようにクリックする。

適当にクリップを作成するとピアノロールが出てくるので、ここでキーボードのAをドに見立てて鍵盤の要領で押していくと、割り当てられた音が鳴る。このときデフォルトだとC3にになっているはずなので、Zを押してオクターブをC1まで下げることを忘れないようにする。鍵盤を押すとピアノロールで該当する鍵盤が赤くなるので何を押したかがわかる。もしその鍵盤に先程スライスした音源が割り当てられていれば音が鳴る。(キー配置に関してはこの記事を参照)

音がわかったら、あとは適当にピアノロールを使ってクリップ内にノートを配置していく。

配置したらスペースキーを押すか、画面上部の再生ボタン(三角)を押して再生。するとこういう感じに音が鳴らせます。

soundcloud.com

少しAbleton Liveの使い方を学べました。

YAMAGUCHI::weblogの2021年を振り返る

はじめに

こんにちは、Cloud Operations担当者です。2021年も最後日です。更に重要なのは本日は私の誕生日であることです。例のやつを貼りました。よろしくお願いします。

www.amazon.jp

1年を通じてコロナ禍だった年となりました。相変わらずオミクロン株やさらなる新種株が出てくるなど、予断を許さない状況ではありますが、来年こそは何かしら光明が見えるといいなと思います。

関連エントリ

ymotongpooの2021年

去年立てた目標

去年の振り返りでは次のような目標を立てていました。これをベースに振り返っていこうと思います。

  • オブザーバビリティ関連の展開
    • OpenTelemetryは安定版がでるので、イベントを行っていきたい
    • Collector関連でOSSを出したい
    • Go関連だとPBTの知見をもう少し深めて、オブザーバビリティに絡めていきたい
  • 執筆・翻訳・監訳をすすめる
    • 執筆はラフでもいいので書いてしまおうと思う
    • 翻訳はコツコツ
    • 監訳の原著レビューを早めに終わらせて、早く監訳プロセスに入れるよう企画を通す
  • FP業の展開
    • 今年は一切できなかったので、ファイナンシャル・プランニング関連でなにかしたい
  • 自作キーボード
    • お店の赤字の改善
    • 自分でキーボードの基板を設計する

2021年はコロナ禍において終始在宅勤務の1年でした。*1 2020年に比べたときの変化で言えば、社会もようやく恒常的に在宅勤務を行うことに対しての覚悟や諦めがついたのか、在宅勤務が当たり前になり、また在宅勤務が常態化してから2年目になったということで生活リズムも安定しました。

仕事

勤続10年

2011年4月にGoogleに転職して勤続10年となりました。細かな話は下のエントリーで書いたので割愛。

ymotongpoo.hatenablog.com

とりあえず勤続10年でなにかあるかなと思ったけど会社からはDoodleの印刷されたものが送られてきただけで、賞与も休暇もない感じでした。さすがにちょっとそれはないんじゃないの、と思ったけど、まあいいや。*2

オブザーバビリティ関連の展開

OpenTelemetryは2020年の予定ではトレース、メトリクスともに安定版リリースがされる予定だったのですが、相変わらずの楽観的な計画だったのでトレースだけが安定版リリースで、メトリクスの安定版リリースは2022年上半期に順延されました。というわけで、イベントは安定するまで先送りにしてきました。来年はOpenTelemetryのメトリクスの安定版のリリースは確実なので、来年こそはOpenTelemetryイベントをやっていきたいですね。

仕事では外に出て活動をすることよりもOpenTelemetry関連の機能をGoogle Cloud内で充実させていく方向の仕事が増え、またコードを書く仕事よりも、製品の方向性に関しての提案や、Google Cloud内の製品のOpenTelemetry対応などの調査と今後の計画を立てたりと、PdMと製品開発チームの間での業務が多かったように思います。Collector関連のOSSGPU周りのものを本体に入れようと試みたのですが、メンテナーと意見が合わず、とりあえず自社のOrganizationで出していく方向で仕切り直すことにしました。

対外的にはおかげさまでSRE関連やオブザーバビリティ関連でお呼びいただくことが何度かあり、国内外のいくつかのイベントやポッドキャストでゲストに呼んでいただきました。

forkwell.connpass.com

e34.fm

dev.to

今年は社内仕事がだいぶ多かったので、来年はもう少しブログ等も含めて露出を行っていければと思っています。

Go関連

今年はGo関連は春にGo Conference 2021 Spring Onlineの開催に関わりました。

gocon.jp

少しずつスタッフが増えてきて、本当にありがたい限りです。秋にはkyoto.goとumeda.goの方々を中心に開催していただいて、成功裏に終わり今年もGoConが無事に開催されたことにほっと胸をなでおろしています。

来年の4月23日にGo Conference 2022 Spring Onlineが開催されることは決定しているわけですが、それと前後して良いお知らせが他にもできれば良いなと思っています。

gocon.jp

来年も引き続き @tenntenn@micchiebear を始め、多くの方たちと一緒に楽しく運営できるといいなと思います。

執筆・翻訳・監訳

このカテゴリーでいうと、去年から監訳を進めていた「SREの探求」が9月に出版されました。

本書の出版の経緯に関しては下記エントリーで書きました。

ymotongpoo.hatenablog.com

他にも並行していくつか案件があるのですが、相変わらず進捗は芳しくないので時間を見てやっていくしかないですね。来年も最低1冊監訳中の本が出せると思いますので、ぜひお楽しみに。それとは別に翻訳のほうが一向に進まなかったのでこちらは少しずつでもやっていかないと。

趣味

自作キーボード

お店の赤字は改善して、プラスマイナスゼロくらいにはなったのですが、結局また仕入れなどがあるので恒常的にプラスに転じるのはなかなか難しいなと感じています。また世界的な半導体不足がまさか自分にも直接的に影響するとは思わなかったのですが、キーボードの制御に使うマイコンも大幅に値上がりし、仕入れ値が一昨年の倍近くに上がっています。こうなってくると販売価格をかなり上げなければ売れば売るだけ赤字になる状況になってしまうので厳しい限りです。おそらく値上げをすることになると思います。

しかしおかげさまで1年間ほそぼそとでもECサイトの運営をすることで小規模小売事業者の様々な苦労であったりコスト感がわかりました。自分が通販で何かを購入するときにはどれくらいの原価がかかっているのかなど、なんとなく想像できるようになって、より販売者を身近に感じられるようになりました。

今年は本業のほうの業務が増えたのでなかなかキーボードの設計ができなかったですが、今年はなんとか一つ設計したいと思っています。

キャンプ

去年は一年中家にいて外出もろくにできず、本当に精神衛生に良くなかったので、今年は一念発起して車を購入し、時間と状況が許す限りキャンプに行っていました。初期出費はかなり大きかったですが、自分の精神衛生だけでなく、家族の精神衛生やコミュニケーションのためにすごく良い趣味になったなと感じています。この状況なので相変わらず海外に行くことはできず、国内旅行をするにも移動手段や場所が限られる中で、キャンプは人との距離が保たれつつ、自然が多い場所に行けるとても良い気分転換になっています。

初期出費は大きかったものの、ある程度道具が揃ったあとは交通費と食費、キャンプ場代ぐらいしかかからないので家族で行くにしても普通の旅行と比べるとだいぶ安上がりで済んでいるのもありがたいです。来年もこの熱は冷める気がしないのと、気軽に他の形式の旅行ができる状況にはならないと思っているので、引き続きキャンプを楽しんでいきたいと思います。

語学

今年の頭からまたDuolingoを復活してコツコツと続けています。

以前Duolingoを試したときは日本語ベースで他の言語を学ぶ教材を見たときにあまり品質が良くないなと思ったので、英語ベースで中国語とスペイン語を勉強しています。言語によってコンテンツの量や品質に差があり、英語ベースだとスペイン語とフランス語のコンテンツはものすごく多く、中国語も悪くはないのですがHSK3級程度のコンテンツで終わってしまうので、ここから先のレベルの内容がほしいなと感じています。中国語は一通りコースを終えてしまったので今は復習しかしていません。一方でスペイン語は先にも述べたとおりコンテンツが充実していて、今はもっぱらスペイン語をメインに勉強しています。さらに遊びでフランス語、ロシア語、韓国語も簡単には読めるようになっておきたいと思い、スペイン語に飽きたときにどれかの言語をちまちまと進めています。

Duolingoは隙間時間でできるのが本当に素晴らしく、1日1レッスンでも達成感を得られるのが良くできているなと感じます。来年にはDELEHSKを受けてみたいなと思います。

来年に向けて

というわけで来年はこんな感じでやっていきたいと思います。(一部去年からの続き)

  • オブザーバビリティ関連の展開
    • OpenTelemetryは安定版がでるので、イベントを行っていきたい
    • Collector関連でOSSを出したい
  • Goコミュニティ関連
    • Go Conference 2022 Springの運営を無事終わらせる
    • Gophers Japanの運営体制を確立する
  • 執筆・翻訳・監訳をすすめる
    • 翻訳はコツコツ
    • いま行っている監訳を終わらせる
  • 自作キーボード
    • 自分でキーボードの基板を設計する
  • キャンプ
    • 引き続きキャンプ道具の洗練をさせていく
    • 悪天候キャンプに慣れる(雨キャンプは未経験)
    • ソロキャンプの充実
  • 語学
    • DELEかHSKを受ける

*1:最後の2ヶ月は多少オフィス勤務はあったものの、同僚含め在宅勤務がメインだった

*2:昔はサバティカル休暇1ヶ月もらえたんだけど、その後に辞める人が多すぎるってことでいつの間にかそれも消滅してた

Go製アプリケーションのコンテナ化にはkoを推したい

はじめに

こんにちは、Google Cloudでオブザーバビリティを担当しているものです。Cloud Operations suiteをよろしくおねがいします。(宣伝終わり)

この記事はGo Advent Calendar 2021 その1の22日目の記事です。昨日は @sago35tk さんの「ESP32 向けに TinyGo をセットアップする」でした。TinyGoのコアな情報を日本語で教えてくれるtakasagoさんには本当にいつも感謝しています。

さて、今日はGo製のアプリケーションをdockerlessでコンテナ化できるkoの紹介をします。koは本当にイチオシのツールで、みんなに使ってもらいたいのでぜひ使ってください。

github.com

DockerによるGo製アプリのコンテナ化

まず最もポピュラーと思われるDockerを用いた場合のGo製アプリケーションのコンテナ化の方法についておさらいします。Go製のアプリケーションで最もシンプルな構成の場合、go build をして特定のパスに置いた後、アプリケーションが使うポートを EXPOSE して、CMD もしくは ENTRYPOINT でそのパスを指定してあげる、というような形になります。

具体的には、次のような簡単なGoのアプリケーションがあった場合

package main

import (
    "log"
    "net/http"
)

const port = "8888"

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, world!"))
    })
    log.Println("starting server on :" + port)
    if err := http.ListenAndServe(":"+port, nil); err != nil {
        log.Fatalf("error running server: %v", err)
    }
}

Dockerfileとして次のようなものを用意する、ということです。

FROM golang:1.17.5-bullseye as builder
WORKDIR /dist
RUN go build -o server

FROM gcr.io/distroless/base-debian11
WORKDIR /app
COPY --from=builder /dist/server /app/server
EXPOSE 8888
CMD ["/app/server"]

koを使った場合

しかしながら、これだけのためにわざわざDockerをいれたいかと言われたら、自分は入れたくありません!そこでkoを使います。

github.com

本日二回目の埋め込みリンクです。koはGo製アプリケーション専用のDockerlessコンテナビルドツールなのですが、たとえば上のアプリケーションの場合は、go build を叩くかのように、設定ファイル等用意する必要なく、この ko publish コマンド一発でコンテナが作れます。

$ ko publish --local --base-import-paths .
2021/12/22 01:08:18 Using base gcr.io/distroless/static:nonroot for test
2021/12/22 01:08:19 Building test for linux/amd64
2021/12/22 01:08:20 Loading ko.local/test:4a9444968723e9d5d24d25b07aaaa504c9ea8a1921273a3753028e5e6d9f5dc9
2021/12/22 01:08:20 Loaded ko.local/test:4a9444968723e9d5d24d25b07aaaa504c9ea8a1921273a3753028e5e6d9f5dc9
2021/12/22 01:08:20 Adding tag latest
2021/12/22 01:08:20 Added tag latest
ko.local/test:4a9444968723e9d5d24d25b07aaaa504c9ea8a1921273a3753028e5e6d9f5dc9

いろいろオプションが付いていますが、これは気にしないことにして、このコマンドによって ko.local/test というイメージができました。これは go.mod でモジュール名を test にしているからです。また ko.localレジストリ名が自動でprefixになっているということです。( --local オプションをつけたことによる。)

たとえば KO_DOCKER_REPO という環境変数gcr.io/foo/bar を指定して、アプリケーションのモジュール名を github.com/xxx/yyy とした場合には、コンテナイメージ名は gcr.io/foo/bar/github.com/xxx/yyy としてくれる、ということです。

koのコマンドがGo製のアプリケーションをビルドした後、よしなにそれをコンテナ内に配置し、エントリーポイントの設定をしてくれ、設定したコンテナレジストリ向けの名前でイメージを作成してくれます。簡単ですね!

ko のインストール

さて、koは簡単そうだ、という雰囲気がわかったところで、ko自体のインストール方法ですが、これも超簡単です。ko自体もただのGo製ツールですので、Goが手元にインストールされている人であれば

go install github.com/google/ko@latest

これでおしまいです。Dockerも必要ありません。macOSな環境では本当にこれは便利ですね!

当然バイナリ配布もされているので、手元にダウンロードしてくればそれで終わりです。各種方法は公式ドキュメントを見てください。

ko の挙動のカスタマイズ

とりあえず最低限動かせるコマンドを紹介したので、次に ko の挙動を変えたい場合にどうするかを紹介します。たとえば、koで作るコンテナのベースイメージを変更したい場合にはどうしたらいいでしょうか。(デフォルトは gcr.io/distroless/static:nonroot )あるいはGoのビルドの際に必要な環境変数を渡したい場合にはどうしたらいいでしょうか。こうしたカスタマイズは .ko.yaml というYAMLファイルで設定します。例えば次のような具合です。

defaultBaseImage: gcr.io/distroless/base-debian11

builds:
  - id: main
    dir: .
    main: .
    env:
      - CGO_ENABLED=0

ko とコンテナランタイムとの連携

さらに ko が便利なのはコンテナランタイムと連携する場合です。たとえば Kubernetes の deployment のYAML内でコンテナのイメージを指定しますが、そこを ko の記法を使ってコンテナを指定しておくことができます。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-deployment
spec:
  replicas: 3
  ...
  template:
    spec:
      containers:
      - name: my-app
        image: ko://github.com/my-user/my-repo/cmd/app

これを ko resolve -f deployment.yaml で渡してあげると、まず github.com/my-user/my-repo/cmd/app にあるアプリケーションを ko でビルドして、環境変数 KO_DOCKER_REPO で指定しているコンテナレジストリにプッシュして、さらに ko://... の文字列をそのイメージのパスで置き換えるということをした deployment.yaml の値を返してくれます。これを kubectl apply にパイプで渡してあげたりするわけです。

apply をするだけであれば ko apply というショートカットも用意してくれています。とにかく雑にGo製アプリケーションをコンテナ化してどこかにデプロイするのがとても簡単になります。

Dockerに投げる場合でも

docker run --rm -p 8888:8888 $(ko publish --local .)

というようなワンライナーで上げられたり、Cloud Runに投げるときは

gcloud run deploy --image=$(ko publish --local .)

という具合にできます。

ko とツールの連携

さきほどちらりと kubectl との連携させるための ko resolve というコマンドを紹介しましたが、Kubernetesを扱う場合、たとえば自分は skaffold を使ってテスト環境を上げたりしています。

skaffoldでは最近 ko がサポートされたので、skaffold.yaml にkoを指定するだけで一気通貫でコンテナのビルドからKubernetesクラスタへのデプロイまでいけます!

build:
  artifacts:
  - image: my-simple-go-app
    ko:
      fromImage: gcr.io/distroless/base-debian11
      labels:
        org.opencontainers.image.licenses: Apache-2.0
        ...

おわりに

大変雑にkoを紹介しましたが、これは概ね ko の公式ドキュメント(=README)に記載されているものです。

github.com

ただいかんせんぱっと見が分かりづらいと思われるので「本当に簡単に使えます!」ということだけにフォーカスして紹介しました。来月にはDocker Desktopの有料化も始まります。コンテナ化を行うだけであれば、いまや選択肢は数多くありますので、koもその一つとして知っておいて損はないと思います。ぜひ試してみてください。