Docker データ管理、ミラーを作成するための Dockerfile

Docker を使用するプロセスでは、ユーザーは多くの場合、アプリケーションによって生成されたデータをコンテナー内で表示したり、コンテナー内のデータをバックアップしたり、複数のコンテナー間でデータを共有したりする必要があり、これには必然的にデータ管理が必要になります。コンテナの操作。

コンテナ内のデータを管理するには、主に 2 つの方法があります。

  • データ量
  • データボリュームのドテナー

1. データ量(コンテナとホスト間のデータ共有)

データ ボリュームは、コンテナ内に配置され、コンテナによって使用される特別なディレクトリです。ホスト マシンのディレクトリをデータ ボリュームにマウントすることができ、データ ボリュームに対する変更操作がすぐに確認でき、データの更新がイメージに影響を与えないため、ホスト マシンとデータ ボリューム間のデータの移行が実現します。コンテナ。データ ボリュームの使用は、Linux でのディレクトリのマウント操作に似ています。

コンテナー内のデータを永続化するには、ホスト ディレクトリをコンテナーにマウントします。

一般に、マウントはコンテナーの作成時にのみ推奨され、コンテナーの起動後にマウントすることは推奨されません。コンテナーを起動してからマウントすると、構成ファイルを変更する必要があり、マウントが成功しない可能性があるためです。

docker run -v 数据卷              #在容器内创建数据卷
 ​
 docker run -v 宿主机目录:数据卷    #将宿主机目录挂载到容器中
 #注意:宿主机本地目录的路径必须是使用绝对路径。如果路径不存在,Docker会自动创建相应的路径。
 #挂载后的目录默认可读可写
 
 #如果希望挂载后的目录为只读目录,可以在挂载时加:ro参数
 docker run -v 宿主机目录:数据卷:ro    #将宿主机目录挂载到容器中,只可读
[root@yuji ~]# ls /var/share      #创建数据卷前,该目录不存在
 ls: 无法访问/var/share: 没有那个文件或目录
 ​
 #将宿主机目录/var/share挂载到容器中的/data1。
 #注意:宿主机本地目录的路径必须是使用绝对路径。如果路径不存在,Docker会自动创建相应的路径。
 #-v选项可以在容器内创建数据卷
 [root@yuji ~]# docker run -v /var/share:/data1 --name web1 -itd centos:7 /bin/bash
 670bf71814364638c4b21a1fb13bcf95c6a2125cd379a5717061d41f9673b0fe
 [root@yuji ~]# ls /var/share -d     #自动创建了该目录
 /var/share
 #进入容器
 [root@yuji ~]# docker exec -it web1 bash 
 [root@670bf7181436 /]# ls            #容器中自动创建了/data1目录
 anaconda-post.log  data1  etc   lib    media  opt   root  sbin  sys  usr
 bin                dev    home  lib64  mnt    proc  run   srv   tmp  var
 [root@670bf7181436 /]# echo "this is web1"> /data1/abc.txt   #向数据卷中写入数据
 [root@670bf7181436 /]# exit    #退出容器
 exit
 #返回宿主机进行查看
 [root@yuji ~]# cd /var/share
 [root@yuji share]# ls
 abc.txt
 [root@yuji share]# cat abc.txt      #可以看到容器中写入的数据,数据同步成功
 this is web1
 ​
 ​
 #在宿主机目录中写入数据,之后进容器中查看
 [root@yuji share]# cp /etc/passwd ./
 [root@yuji share]# ls
 abc.txt  passwd
 [root@yuji share]# docker exec -it web1 bash      #进入容器
 [root@670bf7181436 /]# ls /data1   
 abc.txt  passwd                     #完成了数据同步

2. データボリュームコンテナ(コンテナ間でのデータ共有)

コンテナ間で一部のデータを共有する必要がある場合、最も簡単な方法はデータ ボリューム コンテナを使用することです。データ ボリューム コンテナーは、他のコンテナーがマウントして使用できるデータ ボリュームを提供する通常のコンテナーです。

#创建数据卷容器web2。创建/data1和/data2两个数据卷。
 docker run --name web2 -v /data1 -v /data2 -itd centos:7
 docker exec -it web2 bash                #进入web2容器
 echo "this is web2" > /data1/aaa.txt     #向数据卷/data1中写入数据
 echo "this is yuji" > /data2/bbb.txt     #向数据卷/data2中写入数据
 ​
 #使用--volumes-from 来挂载web2容器中的数据卷到新的容器web3
 docker run -itd --volumes-from web2 --name web3 centos:7
 docker exec -it web3 bash       #进入web3容器
 cat /data1/aaa.txt              #查看/data1中的数据是否和web2一致
 cat /data2/bbb.txt              #查看/data2中的数据是否和web2一致

3. コンテナ相互接続 (centos イメージを使用)

コンテナ相互接続は、コンテナ名を通じてコン​​テナ間に専用のネットワーク通信トンネルを確立することです。簡単に言うと、ソース コンテナと受信コンテナの間にトンネルが確立され、受信コンテナはソース コンテナによって指定された情報を参照できるようになります。

例 1: コンテナーを接続する

#创建并运行源容器取名c1
 docker run -itd -P --name c1 centos:7 /bin/bash
 #创建并运行接收容器取名c2,使用--1ink选项指定连接容器c1以实现容器互联。
 docker run -itd -P --name c2 --link c1:C1 centos:7 /bin/bash
 ##--link 容器名:连接的别名
 ​
 #进c2容器,ping c1,通过容器名称或者别名都可以通信
 docker exec -it c2 bash
 ping c1        #ping c1容器名称
 ping C1        #ping c1容器的别名
 PING C1 (172.17.0.5) 56(84) bytes of data.
 64 bytes from C1 (172.17.0.5): icmp_seq=1 ttl=64 time=0.105 ms
 64 bytes from C1 (172.17.0.5): icmp_seq=2 ttl=64 time=0.066 ms
 #可以看到c1容器的IP地址为172.17.0.5
 ​
 #进入c1容器,查看c1的IP地址
 docker exec -it c1 bash
 yum install -y net-tools   #下载网络工具
 ifconfig                   #查看IP为172.17.0.5,和c2中显示的一致
 eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
         inet 172.17.0.5  netmask 255.255.0.0  broadcast 172.17.255.255

4番目、Dockerイメージの作成

ミラーの作成方法には、[既存のミラーをベースに作成する]、[ローカルテンプレートをベースに作成する]、[Dockerfileをベースに作成する]の 3 つの方法があります。

4.1 既存のイメージを基に作成する

(1)首先启动一个镜像,在容器里做修改
 docker run -it centos:7 /bin/bash     #启动容器
 ​
 yum install -y epel-release  #安装epel源
 yum install -y nginx         #安装nginx
 yum install net-tools        #安装tools工具
 nginx                        #启动服务
 netstat -natp |grep 80       #查看端口是否开启
 ​
 docker ps -a   #查看容器ID
 ​
 (2)然后将修改后的容器提交为新的镜像,需要使用该容器的ID号创建新镜像
 docker commit -m "new nginx" -a "yuji" c7f4bc905c29 nginx:centos
 #常用选项:
 -m 指定说明信息;
 -a 指定作者信息;
 -p 生成过程中停止容器的运行。
 c7f4bc905c29  原容器ID。
 nginx:centos  生成新的镜像名称。
 ​
 docker images    #查看生成的新镜像
 docker run -itd nginx:centos bash          #使用新的镜像创建容器
 docker ps -a                                #查看容器状态
 docker exec -it ae8e40e434fe bash           #进入容器
 nginx                                       #启动nginx服务
 netstat -natp |grep 80                      #查看80端口是否开启

4.2 ローカルテンプレートに基づいて作成する

イメージは、オペレーティング システムのテンプレート ファイルをインポートすることで生成できます。テンプレートは、OPENVZ オープン ソース プロジェクトからダウンロードできます。ダウンロード アドレスは次のとおりです。

openvz.org/ ダウンロード/テンプレート/事前作成

#模板里面就是使用docker export 命令导出的容器文件
 ​
 #下载模板
 wget http://download.openvz.org/template/precreated/debian-7.0-x86-minimal.tar.gz
 ​
 #导入为镜像,两种方法
 cat debian-7.0-x86-minimal.tar.gz | docker import - debian:test  #方法一
  
 docker import debian-7.0-x86-minimal.tar.gz -- debian:test  #方法二
 ​
 #查看镜像
 docker images
 ​
 #使用导入的镜像创建容器
 docker run -itd debian:test bash
 docker ps -a

4.3 Dockerfileを元に作成

4.3.1 ユニオンファイルシステム(UnionFS)

UnionFS (Union File System): Union File System (UnionFS) は、階層化された軽量かつ高性能のファイル システムです。これは、ファイル システムの変更を 1 つのサブミッションとしてサポートし、レイヤーごとに重ね合わせますが、異なるディレクトリは以下にマウントされます。同じ仮想ファイル システムです。AUFS、overlayFS、および Devicemapper はすべて UnionFS のタイプです。

Union ファイルシステムは Docker イメージの基礎です。階層化によるイメージの継承が可能で、ベースイメージ(親イメージなし)をベースに、さまざまな具体的なアプリケーションイメージを作成できます。

機能:複数のファイル システムを同時にロードしますが、外部からは 1 つのファイル システムしか見えません。共同ロードでは各層のファイル システムが重ね合わされるため、最終的なファイル システムには基礎となるすべてのファイルとディレクトリが含まれます。

ダウンロード時に表示されるレイヤーは、ジョイント ファイル システムです。

4.3.2 画像読み込み原理

Docker のイメージは実際にはレイヤーごとのファイル システムで構成されており、このファイル システムのレイヤーが UnionFS です。

Bootfs には主にブートローダーとカーネルが含まれています。ブートローダーは主にカーネルのガイドとロードに使用されます。Linux の起動時に、bootfs ファイル システムがロードされます。

Docker イメージの最下層は bootfs で、ブート ローダーとカーネルを含む一般的な Linux/Unix システムと同じです。ブートのロードが完了すると、カーネル全体がメモリ内にあります。この時点で、メモリの使用権は bootfs からカーネルに転送されます。このとき、システムは bootfs もアンインストールします。

rootfs は bootfs の上にあります。これには、一般的な Linux システムの /dev、/proc、/bin、/etc などの標準的なディレクトリとファイルが含まれています。Rootfs は、Ubuntu、Centos など、さまざまなオペレーティング システム ディストリビューションです。

  • Bootfs は、カーネル ブートローダー (カーネルをブートロードする) およびカーネルです。
  • rootfs は、n 個の基本イメージ (基本的な動作環境を提供する) とアプリケーション イメージが重ね合わされる読み取り専用レイヤーです。
  • 実行中のコンテナ インスタンスは、rootfs の上に読み取りおよび書き込み可能なレイヤーを追加します。

 

 

4.3.3 Docker の centos のサイズが 200M しかないのはなぜですか?

合理化された OS の場合、rootfs は非常に小さくてもよく、最も基本的なコマンド、ツール、およびプログラム ライブラリのみを含める必要があるためです。基礎となる層はホスト マシンのカーネルを直接使用するため、rootfs を提供するだけで済みます。異なる Linux ディストリビューションでは、bootfs は基本的に同じですが、rootfs が異なるため、異なるディストリビューションが bootfs を共有できることがわかります。

ほとんどのイメージは汎用ですが、特定のバージョンに基づいて特別に作成されたイメージは、オペレーティング システムの他のバージョンで実行すると問題が発生する可能性があります。

4.3.4 Dockerfile

Docker イメージは特別なファイル システムであり、コンテナの実行に必要なプログラム、ライブラリ、リソース、構成ファイル、その他のファイルを提供するだけでなく、実行用に準備されたいくつかの構成パラメータ (匿名ボリューム、環境変数、ユーザーなど)。イメージには動的データが含まれておらず、その内容は構築後に変更されません。

イメージのカスタマイズとは、実際には各レイヤーで追加される構成やファイルをカスタマイズすることです。各レイヤーのインストール、構築、動作を変更するコマンドをスクリプトに記述し、そのスクリプトを使用してイメージを構築およびカスタマイズできれば、イメージ構築の透明性とボリュームの問題は解決されます。このスクリプトは Dockerfile です。

独自の追加要件をカスタマイズする必要がある場合、Docketlle 上の命令を追加または変更してイメージを再生成するだけでよく、コマンドを入力する手間が省けます。レイヤーをどのように構築するかを説明します。Dockerfile を使用すると、独自の追加要件をカスタマイズする必要がある場合、Dockerfile 上の命令を追加または変更してイメージを再生成するだけで済み、コマンドを入力する手間が省けます。

Docker イメージを手動で生成するだけでなく、bockerfile を使用してイメージを自動的に生成することもできます。Dockerfile は複数の命令で構成されるファイルで、各命令は Linux のコマンドに対応し、Docker プログラムは Dockerfile 内の命令を読み取って、指定されたイメージを生成します。

Dockerfileの構造は大きく分けて、イメージの基本情報、メンテナ情報、イメージの操作指示、コンテナ起動時の実行指示の4つに分かれます。Dockerfile は 1 行に 1 つの命令をサポートし、各命令は複数のパラメーターを運ぶことができ、「#」で始まるコメントの使用をサポートします。

4.3.5 Dockerイメージ構造の階層化

画像は単一のファイルではなく、複数のレイヤーで構成されています。コンテナーは実際にイメージの上に読み取り/書き込みレイヤーを追加し、実行中のコンテナーで行われたファイルの変更はすべてこの読み取り/書き込みレイヤーに書き込まれます。コンテナーが削除されると、その最上位の読み取り/書き込みレイヤーも削除され、ファイルの変更は失われます。Docker はストレージ ドライバーを使用して、イメージの各レイヤーのコンテンツと、読み取りおよび書き込み可能なレイヤーのコンテナー レイヤーを管理します。

(1) Dockerfile 内の各命令は、新しいイメージ レイヤーを作成します。

(2) 画像レイヤーはキャッシュされて再利用されます。

(3) Dockerfile の命令が変更された場合、コピーされたファイルが変更された場合、またはイメージのビルド時に指定された変数が異なる場合、対応するイメージ レイヤー キャッシュは無効になります。

(4) ある層のミラーキャッシュが無効な場合、それ以降のミラー層のキャッシュも無効になります。

(5) イメージ レイヤーは不変です。レイヤーにファイルを追加し、次のレイヤーでファイルを削除した場合、ファイルはイメージに引き続き含まれますが、Docker コンテナーにはファイルが表示されなくなります。

bootfs:  bootfs ローダー + カーネル、コンテナーはカーネルを共有するため、すべて同じ bootfs を持ちます。

Rootfs 読み取り専用レイヤー:  Rootfs は複数のレイヤーを持つことができ、外側からは全体のように見えます。

ミラーを使用してコンテナー インスタンスを実行する場合、読み取りおよび書き込み可能なレイヤーが rootfs 読み取り専用レイヤーにマウントされます。

 

 

2. Dockerfile操作コマンドの説明

Dockerfile の概要:

Dockerfile は、実際には Docker イメージを構築するために使用するソース コードです。もちろん、これはいわゆるプログラミングのソース コードではなく、コマンドの組み合わせです。ロジックと構文形式を理解していれば、Dockerfile を記述することができます。

簡単に言うと、Dockerfile の役割: ユーザーが Docker イメージをカスタマイズできるようにします。作業環境のニーズは多様であるため、ネットワーク上のミラーリングでは現実のニーズを満たすことが困難です。

Dockerfile の一般的なコマンド:

2.1 画像から

新しいイメージのベースとなるベース イメージを指定します。最初の命令は FROM 命令である必要があり、作成されるイメージごとに FROM 命令が必要です。

2.2 管理者の名前

新しいイメージのメンテナ情報を示します

2.3 RUNコマンド

ベース ミラーでコマンドを実行し、新しいミラーにコミットします。

実行コマンドの数を減らしてみてください。

  • コマンドが長い場合は、\ を使用して改行できます。
  • ; または && を使用して複数のコマンドを 1 つのコマンドに結合し、ミラーリング レイヤの数を減らすことができます。

2.4 エントリーポイント

ENTRYPOINT ["要运行的程序","参数1","参数2"]

コンテナーの起動時に最初に実行されるコマンドとそのパラメーターを設定します。

docker run --entrypointイメージ内の ENTRYPOINT ディレクティブの内容は、次のコマンドを使用してオーバーライドできます。

两种格式:
 ​
 exec格式(数值格式):ENTRYPOINT [“命令”,“选项”,“参数”]
 ​
 shell格式:ENTRYPOINT 命令 选项 参数

最初の 4 つのコマンドは、大まかな鏡像を作成します。

2.5CMD

CMD ["要运行的程序","参数1","参数2"]

コンテナーの起動時にデフォルトで実行されるコマンドまたはスクリプト、Dockerfile には CMD コマンドを 1 つだけ含めることができます。複数のコマンドを指定した場合は、最後のコマンドのみが実行されます。

docker の実行中にコマンドが指定されている場合、またはイメージに ENTRYPOINT がある場合、CMD は上書きされます。

CMD は、ENTRYPOINT コマンドのデフォルト パラメータを提供できます。

两种格式:
 ​
 exec形式:CMD [“要运行的程序”,“参数1”, “参数2”]
 ​
 shell形式: CMD 命令 参数1 参数2

ENTRYPOINT と CMD の共存: ENTRYPOIN はコマンドを指定し、CMD はパラメータを渡します

コンテナー ランタイムの優先順位:

docker run --entrypoint > Dockerfile ENTRYPOINT > docker run コマンド> Dockerfile CMD

ENTRYPOINT と CMD の違い:

  1. ENTRYPOINT は、コンテナーの起動時に実行する最初のコマンドを設定します。CMD は、コンテナーの起動時にデフォルトで実行されるコマンドです。複数の CMD コマンドが指定された場合、最後のコマンドのみが実行されます。
  1. docker run 中にコマンドが指定されている場合、またはイメージ内に ENTRYPOINT がある場合、CMD は上書きされ、CMD 内のコマンドがパラメータとして ENTRYPOINT に渡されます。
  1. CMD は ENTRYPOINT のパラメータを渡すことができます。

2.6 EXPOSEポート番号

新しいイメージが Docker にロードされるときに開くポートを指定します。

ポートを公開するために使用されます。そうでないと、ポート マッピングが行われていても、外部からそのポートを見つけることができません。

2.7 環境

ENV 环境变量 变量值

後続の RUN で使用される環境変数の値を設定します。

2.8 追加

ADD 源文件/目录 目标文件/目录

ソース ファイルをイメージの指定されたパスにコピーします。ソース ファイルは、Dockerfile または URL と同じディレクトリにある必要があります。(URLパス、オンラインパス)

以下の注意事項があります。

 1、
 如果源路径是个文件,且目标路径是以 / 结尾, 则docker会把目标路径当作一个目录,会把源文件拷贝到该目录下。
 如果目标路径不存在,则会自动创建目标路径。
 ​
 2、
 如果源路径是个文件,且目标路径是不以/结尾,则docker会把目标路径当作一个文件。
 如果目标路径不存在,会以目标路径为名创建一个文件,内容同源文件。
 如果目标文件是个存在的文件,会用源文件覆盖它,当然只是内容覆盖,文件名还是目标文件名。
 如果目标文件实际是个存在的目录,则会源文件拷贝到该目录下。注意, 这种情况下,最好显示的以/结尾,以避免混淆。
 ​
 3、
 如果源路径是个目录,且目标路径不存在,则docker会自动以目标路径创建一个目录,把源路径目录下的文件拷贝进来。
 如果目标路径是个已经存在的目录,则docker 会把源路径目录下的文件拷贝到该目录下。
 ​
 4、
 如果源文件是个归档文件,则docker会自动帮解压。(解压后复制源目录到镜像中的目录)
 URL下载和解压特性不能一起使用。任何压缩文件通过URL拷贝,都不会自动解压。
 (不支持下载和解压一起使用,下载就不会解压。即只解压本地压缩包,不会解压下载的压缩包)
  • ADD の利点: <ソース ファイル> が tar 圧縮ファイルで、圧縮形式が gzip、bzip2、xz の場合、自動的に <ターゲット パス> にコピーされて解凍されます。
  • ADD の欠点: Tar アーカイブは解凍せずにコピーできません。イメージ ビルド キャッシュが無効になるため、イメージ ビルドが遅くなる可能性があります。自動解凍が必要かどうかで使用するかどうかを決定できます。

2.9 コピー

COPY 源文件/目录 目标文件/目录

ローカル ホスト上のファイル/ディレクトリのみをターゲットの場所にコピーします。ソース ファイル/ディレクトリは Dockerfile と同じディレクトリにある必要があります。

ADD と COPY の比較: (同じ要件の下では COPY が公式に推奨されます)

1.共通点:

ADD と COPY はどちらもローカル ファイルをイメージにコピーできます。

2. 違い:

ADD: 圧縮ファイルの場合、ADD はコピー後に自動的に解凍します。ソース ファイルをダウンロードするための URL パスもサポートしていますが、URL ダウンロードと解凍機能を併用することはできず、URL によってコピーされた圧縮ファイルは自動的に解凍されません。

COPY: 圧縮ファイルの場合、COPY では解凍できません。また、COPY はローカル ファイルのみをコピーでき、URL パスのコピーはサポートされていません。

2.10 ボリューム [「ディレクトリ」]

コンテナ内にマウント ポイントを作成します (つまり、データ ボリュームを作成します)。

2.11 USER ユーザー名/UID

コンテナを実行する際のユーザーを指定します。(ユーザー切り替え用)

2.12 WORKDIR パス

後続の RUN、CMD、ENTRYPOINT の作業ディレクトリを指定します。(コンテナ内のディレクトリを切り替えるため)

CMD は、ENTRYPOINT コマンドのデフォルト パラメータを提供できます。

workdir /opt  #切换镜像层
 ​
 run cd /opt   #会添加镜像层

2.13 ONBUILD コマンド

生成されたイメージがベース イメージとして使用されるときに実行するコマンドを指定します。

ONBUILD 命令が Dockerfile に追加された場合、その命令は、Dockerfile を使用してイメージ (A イメージなど) をビルドすることに大きな影響を与えません。

ただし、A ミラーに基づいてミラー (B ミラーなど) を構築するために新しい Dockerfile を作成する場合、A ミラーを構築する Dockerfile 内の ONBUILD コマンドがこの時点で有効になります。最初に実行されます。 ONBUILD 命令で指定された命令は、他の命令が実行される前に実行されます。

(つまり、プライベートグッズを追加します。このコマンドは私用ではなく、他のミラー用です)

2.14 成長率

イメージのコンパイル時に追加されるパラメータを設定します。

ARG コマンドは、docker build がイメージをビルドするときに指定されたパラメーターを参照できます。つまり、パラメーターを参照する効果を実現します。

ENV ディレクティブで定義された環境変数は、常に同じ名前の ARG ディレクティブをオーバーライドします。

ARG CONT_IMG_VER      #Dockfile中指定变量名 
 ​
 ENV CONT_IMG_VER=v1.0.0 
 ​
 RUN echo $CONT_IMG_VER  #AEG和ENV定义的变量名,不要重复,不然最后echo的是ENV定义的值
 ​
 docker build --build-arg CONT_IMG_VER=v2.0 .     #构建镜像时传入变量值
 ​
 #因为AEG和ENV定义的变量名重复了,ENV指令定义的环境变量始终会覆盖同名的ARG指令,所以最后输出的是ENV定义的值。

2.15 鏡像を作成する

Dockerfile を作成した後、docker buildコマンドを使用してイメージを作成できます。

基本的な形式は、docker build [选项] 路径このコマンドが指定されたパス (サブディレクトリを含む) にある Dockerfile を読み取り、そのパスにあるすべてのコンテンツを Docker サーバーに送信し、サーバーがイメージを作成するというものです。したがって、一般的には、Dockerfile を配置するディレクトリは空のディレクトリにすることをお勧めします。

さらに、.dockerignore ファイルを使用して、Docker にパスの下のディレクトリとファイルを無視させることができます (各行に一致するパターンを追加します)。

イメージのラベル情報を指定するには、-t オプションを渡すことができます。

Dockerfile を作成する場合、従うべき厳密な形式があります。

  • 最初の行では、FROM コマンドを使用して、そのベースとなるイメージの名前を示す必要があります。
  • 次に、MAINTAINER コマンドを使用して、イメージを維持するためのユーザー情報を記述します。
  • 次に、RUN 命令/EXPOSE/ADD/ENV/ARG などのミラーリング操作に関連する命令があります。命令が実行されるたびに、新しいレイヤーがベース イメージに追加されます。(複数のコマンドを ; または && で 1 つのコマンドに結合して、ミラー層の数を減らすことができます)
  • 最後に、CMD または ENTRYPOINT コマンドを使用して、コンテナーの起動時に実行されるコマンド操作を指定します。

おすすめ

転載: blog.csdn.net/shitianyu6/article/details/127995026