Docker のマルチステージイメージ構築を理解する

Docker のマルチステージイメージ構築を理解する

Docker テクノロジーは、2013 年の誕生以来 4 年以上にわたって存在しています。日常の開発作業で Docker テクノロジーを受け入れて使用してきた開発者にとって、Docker イメージの構築は当たり前のことになっています。しかし、これは Docker のイメージ構築メカニズムが比較的完璧であることを意味するのでしょうか?いいえ、Docker の担当者はイメージ構築メカニズムを継続的に最適化しています。今年リリースされた Docker 17.05 バージョン以降、Docker はコンテナ イメージのマルチステージ ビルドのサポートを開始しました。

多段階イメージ構築とは何ですか?概念を直接定義するのはあまりにも唐突なので、ここでは秘密にしておきましょう。まず、日常の開発で使用されるイメージ構築方法と、イメージ構築で遭遇する問題から始めましょう。

1. 同型画像の構築

イメージを構築する際の一般的なシナリオは、アプリケーションが開発者自身の開発マシンまたはサーバー上で直接コンパイルされ、コンパイルされたバイナリ プログラムがイメージにインストールされるというものです。このような状況では通常、コンパイル環境がイメージで使用されるベース イメージと互換性があることが必要です。たとえば、Ubuntu 14.04 でアプリケーションをコンパイルし、Ubuntu シリーズのベースイメージに基づいたイメージにアプリケーションを配置します。アプリケーションのコンパイル環境は、それがデプロイされ実行される環境と互換性があるため、私はこの種のビルドを「同型イメージ ビルド」と呼んでいます。つまり、Ubuntu 14.04 でコンパイルしたアプリケーションは、ubuntu:14.04 以降のバージョン (例: 16.04、16.10、17.10 など) に基づくベース イメージでほぼシームレスに実行できます。ただし、Centos などの互換性のないベース イメージでは実行に失敗する可能性があります。

1. 均質画像構築の例

ここでは、均質なイメージを構築する例を示します (以降の章もこの例に基づいています)。注: コンパイル環境は、Ubuntu 16.04 x86_64 仮想マシン、Go 1.8.3、Docker 17.09.0-ce です。

Go で最も一般的な http サーバーを例として使用してみましょう。

  1. // github.com/bigwhite/experiments/multi_stage_image_build/isomorphism/httpserver.go
  2. パッケージメイン
  3.  
  4. 輸入 (
  5. 「ネット/http」  
  6. "ログ"  
  7. 「fmt」  
  8.  
  9. func home(w http.ResponseWriter, req *http.Request) {
  10. w.Write([]byte( "このウェブサイトへようこそ!\n" ))
  11. }
  12.  
  13. 関数main() {
  14. http.HandleFunc( "/" , ホーム)
  15. fmt.Println( "Webサーバーの起動" )
  16. fmt.Println( " -> ポート:1111 でリッスン" )
  17. エラー:= http.ListenAndServe( ":1111" 、nil)
  18. err != nil の場合 {
  19. log.Fatal( "ListenAndServe:" , err)
  20. }
  21. }

このプログラムをコンパイルします:

  1. # ビルド -o myhttpserver httpserver.go
  2. # ./myhttpserver
  3. ウェブサーバーの起動
  4. ->ポート:1111リッスン

この例は、数行のコードしかないため単純に見えますが、Go の net/http パッケージは、多くのシステム コールを含む多くの作業を最下層で実行します。これは、アプリケーションとオペレーティング システム間の「結合」を反映するもので、以降の説明で反映されます。次に、このプログラム用の Docker イメージを構築し、このイメージに基づいて myhttpserver コンテナを起動します。ベースイメージとしてubuntu:14.04を選択します。

  1. // github.com/bigwhite/experiments/multi_stage_image_build/isomorphism/Dockerfile
  2. ubuntu:14.04から
  3.  
  4. コピー ./myhttpserver /root/myhttpserver
  5. chmod +x /root/myhttpserver を実行します。
  6.  
  7. ワークディレクトリ /root
  8. エントリポイント [ "/root/myhttpserver" ]

ビルドを実行します。

  1. ビルドが成功すると、リポジトリは更新されます。
  2. ビルド コンテキストを Docker デーモン送信5.894MB
  3. ステップ 1/5: ubuntu:14.04から
  4. ---> dea1945146b9  
  5. ステップ 2/5: ./myhttpserver /root/myhttpserver をコピーします。
  6. ---> 993e5129c081  
  7. ステップ 3/5: chmod +x /root/myhttpserver を実行します。
  8. ---> 104d84838ab2 で実行中 
  9. ---> ebaeca006490  
  10. 中間コンテナ 104d84838ab2 を削除
  11. ステップ 4/5: WORKDIR /root
  12. ---> 7afdc2356149  
  13. 中間コンテナの取り外し 450ccfb09ffd
  14. ステップ 5/5: ENTRYPOINT /root/myhttpserver
  15. ---> 3182766e2a68 で実行中 
  16. ---> 77f315e15f14  
  17. 中間コンテナ 3182766e2a68 を削除しています
  18. 77f315e15f14 を正常に構築しました
  19. myrepo/myhttpserver:latest のタグ付けに成功しました
  20.  
  21. # Docker イメージ
  22. リポジトリ タグ イメージ ID 作成サイズ 
  23. myrepo/myhttpserver 最新 77f315e15f14 18 秒前 200MB
  24.  
  25. # docker 実行 myrepo/myhttpserver
  26. ウェブサーバーの起動
  27. ->ポート:1111リッスン

上記は最も基本的なイメージ構築方法です。

次に、次の要件に遭遇する可能性があります。

* Go プログラムのビルド環境の設定は、特に多くのサードパーティのオープンソース パッケージに依存する Go アプリケーションの場合、時間がかかることがあります。パッケージのダウンロードには長い時間がかかる場合があります。 Go プログラム構築のために、これらすべての変更可能なものをビルダー イメージにパッケージ化するのが最善です。

* 上記で作成した myrepo/myhttpserver イメージのサイズは 200 MB であり、少し「大きすぎる」ようです。各ホストノード上の Docker にはイメージレイヤーをキャッシュする機能がありますが、より簡潔でコンパクトなイメージを構築したいと考えています。

2. Golang ビルダーイメージの使用

Docker Hub は、Go 開発環境を備えた公式の Golang イメージ リポジトリを提供します。この Golang ビルダー イメージを直接使用して、アプリケーション イメージを構築できます。サードパーティのパッケージに大きく依存する一部の Go アプリケーションでは、この golang イメージをベース イメージとして使用して、専用のビルダー イメージをカスタマイズすることもできます。

golang:latest ベースイメージに基づいて golang-builder イメージをビルドします。 golang-builder イメージをビルドするために Dockerfile.build を記述します。

  1. // github.com/bigwhite/experiments/multi_stage_image_build/isomorphism/Dockerfile.build
  2. golang :latest から
  3.  
  4. ワークディレクトリ /go/src
  5. httpserver.go をコピーします。
  6.  
  7. go build -o myhttpserver ./httpserver.go を実行します。

同じディレクトリに golang-builder イメージをビルドします。

  1. ビルドが成功すれば、Docker は再起動します。
  2. ビルド コンテキストを Docker デーモン送信5.895MB
  3. ステップ 1/4: golang:latestから
  4. ---> 1a34fad76b34  
  5. ステップ 2/4: WORKDIR /go/src
  6. ---> 2361824677d3  
  7. 中間コンテナ 01d8f4e9f0c4 を削除
  8. ステップ 3/4: httpserver.go をコピーします。
  9. ---> 1ff14bb0bc56  
  10. ステップ 4/4: go build -o myhttpserver ./httpserver.go を実行します。
  11. ---> 37a1b76b7b9e で実行中 
  12. ---> 2ac5347bb923  
  13. 中間コンテナ 37a1b76b7b9e を削除しています
  14. 2ac5347bb923 の構築に成功しました
  15. myrepo/golang-builder:latest のタグ付けに成功しました
  16.  
  17. リポジトリ タグ イメージ ID 作成サイズ 
  18. myrepo/golang-builder 最新 2ac5347bb923 3 分前 739MB

次に、golang-builder で構築された myhttpserver に基づいて最終的なアプリケーション イメージを構築します。

  1. # docker作成  --name appsource myrepo/golang-builder:latest  
  2. # docker cp appsource:/go/src/myhttpserver ./
  3. # docker rm -f アプリソース
  4. # docker rmi myrepo/golang-builder:latest
  5. ビルドが成功すると、リポジトリは更新されます。

このコマンドのロジックは、golang-builder イメージに基づいて起動されたコンテナ appsource からビルドされた myhttpserver をホストの現在のディレクトリにコピーし、その後、上記でビルドされた一時コンテナ appsource と golang-builder イメージを削除することです。最後の手順は最初の例と同じで、ローカル ディレクトリに構築された myhttpserver に基づいて最終イメージを構築します。便宜上、この一連のコマンドを Makefile に入れることもできます。

3. 小さめのアルプスの画像を使用する

ビルダー イメージは、最終的なアプリケーション イメージの重量を軽減するのに役立ちません。 myhttpserver イメージのサイズは 200 MB のままです。 「軽量化」するには、ベースイメージを小さくする必要があり、アルパインを選択しました。アルパイン画像のサイズは4M未満です。アプリケーションのサイズを追加すると、最終的なアプリケーション イメージのサイズは 20 MB 未満に縮小されると推定されます。

ビルダーイメージを結合するには、Dockerfile のベースイメージを alpine:latest に変更するだけです。

  1. // github.com/bigwhite/experiments/multi_stage_image_build/isomorphism/Dockerfile.alpine
  2.  
  3. アルパインより:最新
  4.  
  5. コピー ./myhttpserver /root/myhttpserver
  6. chmod +x /root/myhttpserver を実行します。
  7.  
  8. ワークディレクトリ /root
  9. エントリポイント [ "/root/myhttpserver" ]

アルパイン バージョンのアプリケーション イメージをビルドします。

  1. ビルド 1.0.1 以降を実行している場合は、次のコマンドを実行します。
  2. ビルド コンテキストを Docker デーモン送信6.151MB
  3. ステップ 1/5: alpine:latestから
  4. ---> 053cde6e8953  
  5. ステップ 2/5: ./myhttpserver /root/myhttpserver をコピーします。
  6. --->ca0527a62d39  
  7. ステップ 3/5: chmod +x /root/myhttpserver を実行します。
  8. ---> 28d0a8a577b2 で実行中 
  9. ---> a3833af97b5e  
  10. 中間コンテナ 28d0a8a577b2 を削除しています
  11. ステップ 4/5: WORKDIR /root
  12. ---> 667345b78570  
  13. 中間コンテナを削除しています fa59883e9fdb
  14. ステップ 5/5: ENTRYPOINT /root/myhttpserver
  15. ---> adcb5b976ca3 で実行中 
  16. ---> 582fa2aedc64  
  17. 中間コンテナ adcb5b976ca3 を削除しています
  18. 582fa2aedc64 の構築に成功しました
  19. myrepo/myhttpserver-alpine:latest のタグ付けに成功しました
  20.  
  21. # Docker イメージ
  22. リポジトリ タグ イメージ ID 作成サイズ 
  23. myrepo/myhttpserver-alpine 最新 582fa2aedc64 4 分前 16.3MB

16.3MB、確かにサイズが小さくなりました!このイメージに基づいてコンテナを起動し、アプリケーションの実行に問題がないか確認します。

  1. # docker run myrepo/myhttpserver-alpine:latest
  2. standard_init_linux.go:185:実行 ユーザープロセスにより「そのようなファイルまたはディレクトリはありません」というメッセージが表示されました 

コンテナの起動に失敗しました。なぜ? alpine イメージは ubuntu 環境と同じではないためです。これについては以下で詳しく説明します。

2. 異種画像構築

私たちのイメージ ビルダー: myrepo/golang-builder:latest は、golang:latest イメージに基づいています。 golang ベースイメージには、Dockerfile-debain.template と Dockerfile-alpine.template の 2 つのテンプレートがあります。 golang:latest は Debian テンプレートに基づいており、Ubuntu と互換性があります。構築された myhttpserver には、動的共有リンク ライブラリに対して次の条件があります。

  1. # ldd myhttpserver
  2. linux-vdso.so.1 => (0x00007ffd0c355000)
  3. libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007ffa8b36f000)
  4. libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffa8afa5000)
  5. /lib64/ld-linux-x86-64.so.2 (0x000055605ea5d000)

Debian ベースの Linux ディストリビューションは glibc を使用します。しかしアルパインは違います。 Alpine は musl libc の実装を使用します。したがって、上記のコンテナを実行すると、myhttpserver が依存する libc.so.6 が見つからないため、ローダーは終了に失敗します。

このようにビルド環境と実行環境が互換性のない状態を「異種イメージビルド」と呼びます。では、この問題をどう解決すればよいのでしょうか?続けましょう:

1. 静的ビルド

主流のプログラミング言語の中で、Go はすでに移植性の点で最高の言語の 1 つです。特に、ランタイムのすべての C コードが Go で書き直され、libc への依存が最小限に抑えられた Go 1.5 以降では、移植性がさらに向上しました。ただし、一部の機能では、C 実装と Go 実装の 2 つの実装バージョンがまだ提供されています。デフォルトでは、つまり CGO_ENABLED=1 の場合、プログラムとプリコンパイルされた標準ライブラリの両方が C 実装を使用します。この点に関する詳細な議論については、私の以前の記事「Go の移植性についても」を参照してください。ここでは詳細には触れません。したがって、異なる libc 実装を使用する Debian システムと Alpine システムの間には、当然ながら非互換性があります。この問題を解決するために、まず Go プログラムを静的にビルドし、次に静的にビルドされた Go アプリケーションを alpine イメージに配置することを検討します。

Dockerfile.build を変更し、Go ソース ファイルをコンパイルするときに CGO_ENABLED=0 を追加しましょう。

  1. // github.com/bigwhite/experiments/multi_stage_image_build/heterogeneous/Dockerfile.build
  2.  
  3. golang :latest から
  4.  
  5. ワークディレクトリ /go/src
  6. httpserver.go をコピーします。
  7.  
  8. CGO_ENABLED=0 を実行して go build -o myhttpserver ./httpserver.go

このビルダーイメージをビルドします:

  1. ビルドが1.0.0.1の場合、Docker は.
  2. ビルドコンテキストを Docker デーモン送信4.096kB
  3. ステップ 1/4: golang:latestから
  4. ---> 1a34fad76b34  
  5. ステップ 2/4: WORKDIR /go/src
  6. ---> 593cd9692019  
  7. 中間コンテナ ee005d487ad5 を削除
  8. ステップ 3/4: httpserver.go をコピーします。
  9. ---> a095eb69e716  
  10. ステップ 4/4: CGO_ENABLED=0 を実行します go build -o myhttpserver ./httpserver.go
  11. ---> d9f3b3a6c36c で実行中 
  12. ---> c06fe8dccbad  
  13. 中間コンテナ d9f3b3a6c36c を削除しています
  14. c06fe8dccbad を正常に構築しました
  15. myrepo/golang- static -builder:latest のタグ付けに成功しました
  16.  
  17. # Docker イメージ
  18. リポジトリ タグ イメージ ID 作成サイズ 
  19. myrepo/golang- static -builder 最新 c06fe8dccbad 31 秒前 739MB

次に、golang-static-builder で構築された静的に接続された myhttpserver に基づいて、最終的なアプリケーション イメージを構築します。

  1. # docker作成  --name appsource myrepo/golang-static-builder:latest  
  2. # docker cp appsource:/go/src/myhttpserver ./
  3. # ldd myhttpserver
  4. 動的実行ファイルはない
  5. # docker rm -f アプリソース
  6. # docker rmi myrepo/golang- static -builder:latest
  7. ビルド 1.0.1 以降を実行している場合は、次のコマンドを実行します。

新しいイメージを実行します。

  1. # docker run myrepo/myhttpserver-alpine:latest
  2. ウェブサーバーの起動
  3. ->ポート:1111リッスン

注: strace を使用すると、Go が静的にリンクするときに独自のランタイム実装のみを使用し、libc.a のコードを使用しないことを証明できます。

  1. # CGO_ENABLED=0 strace -f go build httpserver.go 2>&1 | grepオープン| grep -o '/.*\.a' > go- static -build-strace-file- open .txt

go-static-build-strace-file-open.txt ファイルを開き、その内容を確認します。 libc.a ファイルは見つかりません (Ubuntu では、libc.a は通常 /usr/lib/x86_64-linux-gnu/ にあります)。これは、go build が libc.a ファイルを開いてその中のシンボル定義を取得しようとしなかったことを意味します。

2. alpine golangビルダーを使用する

私たちの Go アプリケーションは、alpine ベースのコンテナーで実行されます。 alpine golang ビルダーを使用してアプリケーションをビルドできます (静的リンクなし)。前述したように、golang には alpine テンプレートがあります。

  1. リポジトリ タグ イメージ ID 作成サイズ 
  2. golang alpine 9e3f14138abd 7日前 269MB

golang ビルダーの alpine バージョンの Dockerfile の内容は次のとおりです。

  1. //github.com/bigwhite/experiments/multi_stage_image_build/heterogeneous/Dockerfile.alpine.build
  2.  
  3. golang:alpineより
  4.  
  5. ワークディレクトリ /go/src
  6. httpserver.go をコピーします。
  7.  
  8. go build -o myhttpserver ./httpserver.go を実行します。

以降の操作は、前の golang ビルダーと同じです。alpine golang ビルダーを使用してアプリケーションをビルドし、alpine イメージに配置します。ここでは詳細には触れません。

3. 多段階イメージ構築:開発者エクスペリエンスの向上

Docker 17.05 より前では、上記のようにイメージを構築していました。異種イメージ ビルダー モードを使用する場合でも、2 つの Dockerfile を維持し、コンテナーからのアプリケーションのコピー、ビルド コンテナーのクリーンアップ、イメージのビルドなど、docker build コマンドの外部でいくつかの操作を実行する必要があることがわかります。 Docker コミュニティはこの問題を認識し、マルチステージ イメージ構築メカニズム (マルチステージ) を実装しました。

まず、上記の例のマルチステージ ビルドに使用される Dockerfile を見てみましょう。

  1. //github.com/bigwhite/experiments/multi_stage_image_build/multi_stages/Dockerfile  
  2. golang:alpineからビルダーとして 
  3. ワークディレクトリ /go/src
  4. httpserver.go をコピーします。  
  5. go build -o myhttpserver ./httpserver.go を実行します。  
  6. アルパインより:最新 
  7. ワークディレクトリ /root/
  8. --from=builder /go/src/myhttpserver にコピーします。  
  9. chmod +x /root/myhttpserver を実行します。  
  10. エントリポイント [ "/root/myhttpserver" ]

この Dockerfile の内容を読んだ後の第一印象は、前の 2 つの Dockerfile が結合され、各 Dockerfile が個別の「ステージ」になっているということです。確かにその通りですが、この Docker には、さまざまな「ステージ」間の接続を確立するための新しい構文形式もいくつかあります。このような Dockerfile については、次の点を知っておく必要があります。

  • マルチステージ ビルドをサポートする Dockerfile は、複数のビルド ステージ間に内部接続を確立し、次のビルド ステージで前のビルド ステージの成果物を使用できるようにして、ビルド ステージのチェーンを形成します。
  • マルチステージ ビルドの最終結果は 1 つのイメージのみであり、複数の冗長な一時イメージまたは一時コンテナー オブジェクトの生成を回避します。これはまさに必要なことであり、必要なのは結果だけです。

上記の例をマルチステージを使用して構築してみましょう。

  1. ビルドが 0x80000000 の場合、このコマンドは実行されません。
  2. ビルドコンテキストを Docker デーモン送信3.072kB
  3. ステップ 1/9: golang:alpineからビルダーとして
  4. ---> 9e3f14138abd  
  5. ステップ 2/9: WORKDIR /go/src
  6. ---> キャッシュの使用 
  7. ---> 7a99431d1be6  
  8. ステップ 3/9: httpserver.go をコピーします。
  9. ---> 43a196658e09  
  10. ステップ 4/9: go build -o myhttpserver ./httpserver.go を実行します。
  11. ---> 9e7b46f68e88 で実行中 
  12. ---> 90dc73912803  
  13. 中間コンテナ 9e7b46f68e88 を削除しています
  14. ステップ 5/9: alpine:latestから
  15. ---> 053cde6e8953  
  16. ステップ 6/9: WORKDIR /root/
  17. ---> キャッシュの使用 
  18. ---> 30d95027ee6a  
  19. ステップ 7/9: COPY --from=builder /go/src/myhttpserver.  
  20. ---> f1620b64c1ba  
  21. ステップ 8/9: chmod +x /root/myhttpserver を実行します。
  22. ---> e62809993a22 で実行中 
  23. ---> 6be6c28f5fd6  
  24. 中間コンテナ e62809993a22 を削除
  25. ステップ 9/9: ENTRYPOINT /root/myhttpserver
  26. ---> e4000d1dde3d で実行中 
  27. ---> 639cec396c96  
  28. 中間コンテナ e4000d1dde3d を削除
  29. 639cec396c96 の構築に成功しました
  30. myrepo/myhttserver-multi-stage:latest のタグ付けに成功しました
  31.  
  32. # Docker イメージ
  33. リポジトリ タグ イメージ ID 作成サイズ 
  34. myrepo/myhttserver-multi-stage 最新 639cec396c96 約1時間前 16.3MB

この画像を実行してみましょう:

  1. # docker run myrepo/myhttserver-multi-stage:latest
  2. ウェブサーバーの起動
  3. ->ポート:1111リッスン

IV.まとめ

マルチステージイメージ構築により、開発者は Dockerfile を使用して一度に小さなイメージをより簡単に構築できるようになり、優れたユーザーエクスペリエンスが提供され、CI/CD などの自動化システムへの接続が容易になります。ただし、現在、マルチステージ ビルドは Docker 17.05 以降のバージョンでのみサポートされています。この機能を学習して練習したいが環境がない場合は、play-with-docker が提供する実験環境を使用できます。

Dockerラボで遊ぶ

<<:  恒源智成とアリババクラウドが共同でビジネスチェーン全体をカバーするダブル11クラウドカーニバルを開始

>>:  SaaS 無料トライアルの 3 つの秘密

推薦する

Hostus-7 USD/3g RAM/3g Vswap/70 HDD/3T トラフィック

Hostus、新年のプロモーション、ここでは大容量メモリを備えた特別なVPSのみを選択します。母鶏は...

マルチクラウド VS スカイコンピューティング、企業はクラウドコンピューティング戦略をどのように選択すべきでしょうか?

クラウド コンピューティングをどのように使用するかは、企業のアプリケーション戦略アーキテクチャと計画...

推奨: Egihosting 超大型ハードドライブ VPS

Egihosting はストレージベースの VPS を開始しました。必要な場合はぜひご覧ください。デ...

ショッピングモールの商品ページの最適化:キーワードのレイアウトと包含

ショッピングモールサイトである瑞品モールは、主に写真形式のウェブサイトです。お茶業界のインテグレータ...

10万人のネットセレブが5億人のネットユーザーと競争。ネットセレブはライブストリーミング販売でどこまで行けるだろうか?

今年の天猫ダブル11プレセール初日には、約10万人のタオバオキャスターが放送を開始し、約2万のブラン...

2019年のクラウドコンピューティングのレビュー:5G+AI+クラウドがトレンドになり、クラウドゲームが次のホットスポットになる可能性

2019年へのカウントダウンが始まりました。クラウド コンピューティング市場を振り返ると、変化と課題...

Baidu SEO 共通ツールの紹介

SEO担当者は、オリジナルコンテンツの作成、外部リンクの作成、画像の作成など、毎日やるべきことがたく...

クラウドコンピューティングの構成エラーによって生じる脆弱性に対処する方法

大規模なハッキングやエクスプロイトを準備する際、サイバー攻撃者は自身のスキルや狡猾さよりも、被害者の...

有名なブログを作るには、粘り強さだけでは不十分

最近、偶然、Lu Songsong 氏の「有名なブログを作るにはどのくらいの時間がかかりますか?」と...

SEOトレーニングアカデミーは、ウェブサイトのユーザーエクスペリエンスを向上させる10の側面をまとめています

ウェブサイトのユーザーエクスペリエンスを向上させることに関しては、多くの SEO 担当者は無知です。...

Yinke が SEO について簡単に語る: ウェブサイト スパイダーのクローリングを改善する 8 つの方法

SEO 最適化を始めたばかりの方は、検索エンジン スパイダー (検索エンジンが Web ページをクロ...

Pinterest モデルのエコシステムを解明: 半年で 10 社がコンテンツに閉じ込められる

ソーシャル分野では、Facebook がインターネット発展の歴史におけるユニークな花となり、Pint...

権威あるウェブサイトの力を活用してトラフィックを増やす

みなさんこんにちは。A5でまたお会いできて嬉しいです。上記で公開した 2 つの記事は 1,000 回...

Baidu はオリジナルの記事をインデックスするだけでなく、構造にも重点を置いており、組み込まれやすくなります。

ほとんどのウェブマスターの目には、Baidu がウェブサイトの記事を組み込むのは、その質が優れている...