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 つの秘密

推薦する

オンラインマーケティングで売上を獲得するには、まず消費者を獲得する必要がある

消費者は市場の主な消費者として、企業の発展の過去と未来を担っています。昨日の市場では、一部の消費者が...

Amazon Web Services、自社製チップTrainuimを搭載したAmazon EC2 Trn1インスタンスの提供開始を発表

最近、Amazon Web Services は、自社開発チップ Amazon Trainium を...

ウェブサイトの記事ページの構造とコンテンツを最適化するSEOテクニック

多くの場合、Web サイトのメインテキスト ページは、Web サイト上で最も数が多いページになります...

エッジコンピューティング: 古いものは捨て、新しいものを導入する

調査会社MarketsandMarketsによると、モノのインターネットとクラウドコンピューティング...

fanayun: 10% 割引、香港 cn2 vps+ (必須 3 ネットワーク、50G 高防御) 米国 cn2 gia vps、22 元/月、1G メモリ/1 コア/20g SSD

fanayun (Fan Yun) は現在、VPS プロモーションを実施しています: すべての VP...

ウェブサイトの最適化において適切な説明タグを書く3つの主な機能

ウェブサイトの最適化において、ウェブマスターの目にはほとんど役に立たなくなっているタグが 2 種類あ...

企業の中核的な競争力はどこから来るのか?成功するマーケティングは自分自身を知ることから始まります

ほとんどすべての企業のウェブサイト管理者やSEO担当者は、オリジナルコンテンツの作成や外部リンクの宣...

OpenVirtuals - 256m メモリ/512m スワップ/5gSSD/1T トラフィック/年間 24 USD

OpenVirtualsは2009年に設立されました(20年以上の歴史を持つ老舗企業であるInter...

Wishosting-高セキュリティVPS/3.99 USD/2g RAM/200g HDD/無制限トラフィック/Windows

Wishosting さん、ウェブサイトは非常にすっきりしていて、情報はまったくありませんが、この製...

Zhihu がビデオを作る見込みはありますか?

「知乎のおすすめページを更新するたびに、少なくとも1本の動画が表示され、時には2本か3本の動画が表示...

個人ウェブサイト構築経験 ウェブサイト運営に関する様々なトピック

最近ウェブサイトを構築しました。今年2月初旬から6月中旬まで、ウェブサイトのデータのパフォーマンスは...

Nutanix Matt Young: エッジコンピューティングは黄金の10年を迎える

新しい10年を迎えるにあたり、社会経済的要因によりアジアはさらに世界の注目を集めるようになるだろう。...

週刊ニュースレビュー:ダブル11の取引量が記録を更新、百度ウェブサイトの協力が再開

1. ダブル11の電子商取引プロモーション戦争の再現:Tmallが主導権を握り、JD.comは孤立毎...

AppleがiPhoneから個人情報を抜き取っていたことが発覚

ロイター通信によると、アップルは最近、非公開の技術を通じて、テキストメッセージ、連絡先リスト、写真な...

SEOは、生き残り続けるために、包括的かつバランスの取れたトレンドに向かって発展する必要があります。

最近、SEO に打撃を与えた一連の悲劇的な出来事を経験した後、著者の babyliu は突然、SEO...