クラウド コンピューティングとビッグ データ - Kubernetes クラスターの展開 + nginx の完全な展開 (超詳細!)

クラウド コンピューティングとビッグ データ - Kubernetes クラスターの展開 + nginx の完全な展開 (超詳細!)

Kubernetes クラスターのデプロイの基本的な考え方は次のとおりです。

  1. 環境を準備します。

    • 適切なオペレーティング システムを選択する: ニーズに応じてオペレーティング システムとして適切な Linux ディストリビューションを選択し、すべてのノードで同じ選択を行ってください。
    • Docker をインストールする: アプリケーションとコンポーネントをコンテナ化するために使用される Docker をすべてのノードにインストールします。
    • Kubernetes ツールをインストールします: install kubectlkubeadmおよびkubeletクラスターの管理と構成に使用されるツール。
  2. マスター ノード (マスター ノード) をセットアップします。

    • ノードをマスター ノードとして選択します。通常、スレーブ ノードの 1 つがマスター ノードとして選択されます。マスター ノードには、十分なリソースがある任意のマシンを選択できます。
    • マスター ノードを初期化する:kubeadm initコマンドを使用してマスター ノードを初期化し、生成された結合トークンを取得します。
    • ネットワーク プラグインのセットアップ: Flannel、Calico、Weave などの適切なネットワーク プラグインを選択してインストールし、ノード間のネットワーク通信を有効にします。
  3. スレーブ ノード (ワーカー ノード) を追加します。

    • 各スレーブ ノードで結合コマンドを実行します。前に生成した結合トークンを使用して、各スレーブ ノードでコマンドを実行し、kubeadm joinクラスタに結合します。
    • ノード参加の確認: マスター ノードでコマンドを実行し、kubectl get nodesすべてのノードがクラスターに正常に参加していることを確認します。
  4. Web プラグインをデプロイします。

    • 選択したネットワーク プラグインに応じて、その特定の展開と構成に従います。これにより、ノード間のネットワーク通信が確保され、Kubernetes クラスターのネットワーク機能が提供されます。
  5. 他のコンポーネントとアプリケーションをデプロイします。

    • kube-proxy や kube-dns/coredns などの他の必要なコア コンポーネントをデプロイします。
    • アプリケーションまたはサービスをデプロイします。これらは、Kubernetes のデプロイメント、サービス、またはその他のリソース タイプを使用して管理できます。
  6. クラスターのステータスを確認します。

    • kubectl get nodesおよびその他のコマンドを実行して、kubectlクラスター内のノードとコンポーネントが適切に機能していることを確認します。
    • アプリケーションのテスト: クラスターにデプロイされたアプリケーションに対してテストを実行し、アプリケーションが適切に機能し、他のコンポーネントと対話することを確認します。

上記は、Kubernetes クラスターをデプロイする基本的な考え方です。正確な手順と詳細は環境やニーズによって異なる場合がありますが、この簡単な説明は展開プロセスの一般的なフローを理解するのに役立ちます。

次に、実際に kubernetes クラスターをデプロイし、nginx サービスを完成させます。

Kubernetes クラスターのデプロイを開始する

root として次のコマンドを実行します。

1. すべてのノードに Docker をインストールして構成する

1. Dockerに必要なツールをインストールする

yum install -y yum-utils device-mapper-persistent-data lvm2

ここに画像の説明を挿入
2. Alibaba Cloud の Docker ソースを構成する

yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

ここに画像の説明を挿入
3. docker-ce、docker-ce-cli、containerd.ioをインストールします。

yum install -y docker-ce docker-ce-cli containerd.io

ここに画像の説明を挿入
4.ドッカーを起動する

systemctl enable docker	#设置开机自启
systemctl start docker		#启动docker

ここに画像の説明を挿入
5.ミラーアクセラレータの設定
#ミラーアクセラレータの設定、daemon.jsonファイルを新規作成(参考1)

cat <<EOF > /etc/docker/daemon.json
{
    
    
    "registry-mirrors": ["https://b9pmyelo.mirror.aliyuncs.com"]
}

ここに画像の説明を挿入
ここに画像の説明を挿入
6.containerd のデフォルト設定ファイル /etc/containerd/config.toml を生成および変更します (参考 2)

containerd config default > /etc/containerd/config.toml

ここに画像の説明を挿入
ここに画像の説明を挿入
Sandbox_image="registry.k8s.io/pause:3.6"
を Sandbox_image="k8simage/pause:3.6" に変更します。
ここに画像の説明を挿入
ここに画像の説明を挿入

Containerd サービスを再起動します

systemctl daemon-reload  
systemctl restart containerd.service 

ここに画像の説明を挿入
ここに画像の説明を挿入
注: この手順では、イメージ \"registry.k8s.io/pause:3.6\" のプルに失敗しました、ポッドのサンドボックスの作成に失敗しました: registry.k8s.io/pause:3.6 イメージのプルに失敗しました、およびその他の問題 (特定のエラー問題が発生する可能性があります) を解決できます。次のコマンドを実行してログを表示します:journalctl -xeu kubelet)
7. ファイアウォールをオフにします。

systemctl disable firewalld
systemctl stop firewalld

ここに画像の説明を挿入
ここに画像の説明を挿入
8. selinux を閉じる
# selinux を一時的に無効にする

setenforce 0

#または完全に閉じて、/etc/sysconfig/selinux ファイルの設定を変更します

sed -i 's/SELINUX=permissive/SELINUX=disabled/' /etc/sysconfig/selinux

ここに画像の説明を挿入
sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
ここに画像の説明を挿入
9. スワップパーティションを無効にする

swapoff -a

#または永久に無効にし、/etc/fstab ファイル内の swap の行をコメントアウトします。

sed -i ‘s/.*swap.*/#&/’ /etc/fstab

ここに画像の説明を挿入
ここに画像の説明を挿入
10. ブリッジされた IPv4 トラフィックを iptables チェーンに渡すようにカーネル パラメータを変更します
(一部の ipv4 トラフィックは iptables チェーンを通過できません。これは、Linux カーネル内のフィルタにより、各トラフィックが iptables チェーンを通過し、トラフィックが iptables チェーンに入ることができるかどうかを照合します。現在のアプリケーションプロセスを処理するため、トラフィック損失が発生します)、k8s.confファイルを設定します(k8s.confファイルは存在しないため、自分で作成する必要があります)

cat<<EOF> /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables=1
net.bridge.bridge-nf-call-iptables=1
net.ipv4.ip_forward=1
vm.swappiness=0                  
EOF

ここに画像の説明を挿入
ここに画像の説明を挿入

sysctl --system 

#すべてのシステム パラメータを再ロードするか、sysctl -p を使用します。
ここに画像の説明を挿入
ここに画像の説明を挿入
ドキュメント 1: https://yebd1h.smartapps.cn/pages/blog/index?blogId=123605246&_swebfr=1&_swebFromHost=bdlite

2. すべてのノード (個別に指定したノードを除く) に Kubernetes をインストールして構成します。

1. Kubernetes Alibaba Cloud ソースを構成する

cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

ここに画像の説明を挿入
ここに画像の説明を挿入
2. kubeadm、kubectl、および kubelet をインストールします (kubeadm と kubectl は両方ともツールであり、kubelet はシステム サービスです、参照 1)。

yum install -y kubelet-1.14.2
yum install -y kubeadm-1.14.2

ここに画像の説明を挿入
ここに画像の説明を挿入
ここに画像の説明を挿入
ここに画像の説明を挿入
ここに画像の説明を挿入
ここに画像の説明を挿入
3.kubeletサービスを開始します

systemctl enable kubelet && systemctl start kubelet

ここに画像の説明を挿入
ここに画像の説明を挿入
4. 現在のバージョンの初期化構成ファイルを /etc/kubernetes ディレクトリに生成します。

kubeadm config print init-defaults > /etc/kubernetes/init-default.yaml

ここに画像の説明を挿入
1) kube-apiserver が他のコンポーネントにブロードキャストする IP アドレスを指定します。
他のノードが kube-apiserver にアクセスできるようにするには、このパラメーターをマスター ノードの IP アドレスに設定する必要があります
。 [host ip(Intranet)]
この項目はマスターノードのIPアドレスに応じて設定されます。本機の設定は次のとおりです。

advertiseAddress:192.168.95.20

2) イメージをインストールするためのウェアハウス ソースを指定します。Alibaba
Cloud などの国内イメージを使用することをお勧めします:
imageRepository: registry.aliyuncs.com/google_containers
注: 最初に次の初期化の問題が発生します。

failed to pull image registry.k8s.io/kube-apiserver:v1.26.3

この設定を kubeadm init コマンドの次のパラメーターと組み合わせることで、問題を解決できます。

--image-repository=registry.aliyuncs.com/google_containers

つまり、初期化中: kubeadm init --image-repository=registry.aliyuncs.com/google_containers は
後で初期化されます
。 3) /etc/hosts を編集して次の行を追加します。

192.168.95.20 k8s.cnblogs.com #需根据自己主机的IP地址进行修改

ここに画像の説明を挿入
一般に、ステップ 4 で、[kubelet-check] 初期タイムアウト 40 秒が経過するという複雑な問題を解決できます。
参考2:
https://blog.csdn.net/weixin_52156647/article/details/129765134
4) KubernetesとdockerのCgroup Driverをsystemdとして統合し、/
etc/docker/daemon.jsonファイルを修正し、以下のパラメータを追加します。 :

vim /etc/docker/daemon.json 

#すべてのノードのドッカー構成の一貫性を保つために、他のノードのドッカーも変更されました

{
    
    
"registry-mirrors": ["https://b9pmyelo.mirror.aliyuncs.com"],	
"exec-opts": ["native.cgroupdriver=systemd"]
}

ここに画像の説明を挿入
ドッカーをリロードする

systemctl daemon-reload
systemctl restart docker

5.マスター ノードで Kubernetes の初期化を開始する
前に、次のコマンドを実行します。

systemctl restart docker

ここに画像の説明を挿入
kubeletコマンドの補完

echo "source <(kubectl completion bash)" >> ~/.bash_profile
source .bash_profile

ここに画像の説明を挿入
ここに画像の説明を挿入
イメージの
プル Kubernetes クラスターの起動に必要なイメージのリストをリストします。これらのイメージには、コントロール プレーン コンポーネント (kube-apiserver、kube-controller-manager、kube-scheduler など) およびその他の必要なコンポーネント (etcd、CoreDNS など) が含まれており、必要なバージョンと一致するようにタグを変更します。

kubeadm config images list

ここに画像の説明を挿入
ミラーソースとスクリプトプログラムを設定する

vim image.sh
#!/bin/bash
url=registry.cn-hangzhou.aliyuncs.com/google_containers
version=v1.14.2
images=(`kubeadm config images list --kubernetes-version=$version|awk -F '/' '{print $2}'`)
for imagename in ${images[@]} ; do
  docker pull $url/$imagename
  docker tag $url/$imagename k8s.gcr.io/$imagename
  docker rmi -f $url/$imagename
done

ここに画像の説明を挿入
スクリプトを実行する

chmod u+x image.sh
./image.sh
docker images

ここに画像の説明を挿入
docker イメージ docker ウェアハウス内のイメージを確認すると、すべてのイメージが registry.aliyuncs.com/google_containers/ で始まり、その一部が kubeadm config イメージ リストで必要なイメージ名と異なることがわかります。画像名を変更したい、つまり画像を再タグ付けしたい

docker images

結果を示す:

docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.14.10 k8s.gcr.io/kube-apiserver:v1.14.10

docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/kube-controller-manager:v1.14.10 k8s.gcr.io/kube-controller-manager:v1.14.10

docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/kube-scheduler:v1.14.10 k8s.gcr.io/kube-scheduler:v1.14.10

docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/kube-proxy:v1.14.10 k8s.gcr.io/kube-proxy:v1.14.10

docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.1 k8s.gcr.io/pause:3.1

docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/etcd:3.3.10 k8s.gcr.io/etcd:3.3.10

docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/coredns:1.3.1 k8s.gcr.io/coredns:1.3.1

ここに画像の説明を挿入
タグを変更した後、再度確認して、イメージ名とバージョン番号が、「kubeadm config Images list」コマンドにリストされている Kubernetes クラスターの起動に必要なイメージのリストと一致していることを確認します。
ここに画像の説明を挿入
別の方法では、画像を 1 つずつ取得することもできます

kubeadm config images list

ここに画像の説明を挿入

docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.40.10
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-controller-manager:v1.40.10
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-scheduler:v1.40.10
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-proxy:v1.40.10
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.1
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/etcd:3.3.10
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/coredns:1.3.1

ここに画像の説明を挿入
Docker イメージを再度表示します。

docker images

ここに画像の説明を挿入
k8s クラスターをリセットする

kubeadm reset

ここに画像の説明を挿入
ポート占有を解除するには、前回の初期化時に生成された設定ファイルを削除するなどしてください。
次に、クラスターの初期化の正式な実行を開始します。

kubeadm init --apiserver-advertise-address 192.168.95.20 --pod-network-cidr=10.244.0.0/16

ここに画像の説明を挿入
実行メッセージが表示されます:Your Kubernetes control-plane has initialized successfully!クラスターの初期化が正常に完了しました。
ここに画像の説明を挿入
ここに画像の説明を挿入
6. ノードの構成
次の 3 つのコマンドは、一般ユーザーを使用します。

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

7. ノードから参加する場合はコマンドと秘密鍵に注意

kubeadm join 192.168.95.20:6443 --token 7em598.2cwgsvdgga5fohae \
    --discovery-token-ca-cert-hash sha256:9fca7635ebe04c5fe7eccb8c30974ff0e4f7cb08785d1132956be9a800ded442

このキーは安全に保管してください。
ここに画像の説明を挿入
8. ノードの稼働状態(NotReady状態)を確認します。

kubectl get nodes

ここに画像の説明を挿入

3. スレーブノード Kubernetes のインストールと構成

1. スレーブ ノード ソフトウェアをインストールし、上記の 1 番目と 2 番目の手順に従って設定します。
host1の基本設定:
ここに画像の説明を挿入
dockerバージョン
ここに画像の説明を挿入

Kubectl version
kubeadm version
kubelet version

ここに画像の説明を挿入
2. ノードからクラスターに参加します (root ユーザーを使用)

kubeadm join 192.168.95.20:6443 --token 7em598.2cwgsvdgga5fohae \
--discovery-token-ca-cert-hash sha256:9fca7635ebe04c5fe7eccb8c30974ff0e4f7cb08785d1132956be9a800ded442

ここに画像の説明を挿入
注: このステップでは通常、次の問題が発生します。

[ERROR CRI]: container runtime is not running:

これは、サブパッケージとともにインストールされた containerd がデフォルトでコンテナ ランタイムとして無効にするためです。 解決
ここに画像の説明を挿入
策:
1) systemctl status containerdView Status
Active: active (running) を使用して、コンテナが正常に実行されていることを示します。
ここに画像の説明を挿入
2) /etc/containerd/config を確認します。 toml ファイル、これはコンテナーが実行されているときの構成ファイルです
3)

vim /etc/containerd/config.toml

この行が表示された場合は:disabled_plugins : ["cri"]
この行を # でコメントするか、「cri」を削除してください:
#disabled_plugins : [“cri”] または
disabled_plugins : []
4) コンテナー ランタイムを再起動します。

systemctl restart containerd

ここに画像の説明を挿入
参考3: https://blog.csdn.net/weixin_52156647/article/details/129758753

4. マスターノード上で参加しているスレーブノードを表示し、後続の問題を解決します。

kubectl get nodes

ここに画像の説明を挿入

1. この時点で、STATUS には NotReady
ソリューションが表示されます。
1) すべてのクラスター ノードに kubernetes-cni を再インストールします。

yum reinstall -y kubernetes-cni

ここに画像の説明を挿入
ここに画像の説明を挿入
2) マスター ノードにネットワークをインストールします:
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml、ここで 185.199.108.133 raw.githubusercontent.com を追加します。 etc/hosts
(参考 5: https://www.cnblogs.com/sinferwu/p/12726833.html )
3. kubectl get Nodes 実行時の問題
1) 現在のサーバー API グループのリストを取得できませんでした:
解決済み:
コマンドが実行できませんroot として実行する必要があります。
2) kubernetes-admin の問題
K8S 入力 kubectl get ノードは、サーバー localhost:8080 への接続が拒否されました - 正しいホストまたはポートを指定しましたか?
この問題の理由は、kubectl コマンドを kubernetes-admin で実行する必要があることです。 。k8s を再インストールする前に設定を完全にクリアしていないなど、システム環境がクリーンでないことが原因である可能性があります。
解決策:
(1) マスターノードの初期化後に生成された /etc/kubernetes/admin.conf ファイルをスレーブノードの対応するディレクトリにコピーします。

scp /etc/kubernetes/admin.conf  host1:/etc/kubernetes/

ここに画像の説明を挿入
(2) 全ノードに環境変数を設定し、更新する

echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bash_profile 
source ~/.bash_profile

ここに画像の説明を挿入
ここに画像の説明を挿入

https://www.ipaddress.com/ で raw.githubusercontent.com の実際の IP を確認します。

ここに画像の説明を挿入
再実行

kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml

フランネルの取り付けに成功しました。
ここに画像の説明を挿入
kubectl get Nodes コマンドを使用してクラスター ノード リストを再度取得すると、host1 クラスターのステータスが常に NotReady 状態であることがわかり、ログを確認するとエラーが報告されます。

journalctl -f -u kubelet

ここに画像の説明を挿入
ログ情報によると、エラーの原因は、kubelet の設定を /var/llib/kubelet/config.yaml からダウンロードできないことです。

エラーの理由は、おそらく、前に kubeadm init を実行せずに実行したことですsystemctl start kubelet
トークンの更新と再生成を試みることができます。コードは次のとおりです。

kubeadm token create --print-join-command

マスター ノード g 上のトークン コピー出力の内容を更新し
ここに画像の説明を挿入
、それを hsot1 で実行すると
ここに画像の説明を挿入
、問題は正常に解決されます。
クラスターはすべて準備完了状態です。
ここに画像の説明を挿入
参考資料 6: https://www.cnblogs.com/eastwood001/p/16318644.html

5. Kubernetes をテストする

1. マスター ノードで実行します。

kubectl create deployment nginx --image=nginx #创建一个httpd服务测试

ここに画像の説明を挿入

kubectl expose deployment nginx --port=80 --type=NodePort #端口就写80,如果你写其他的可能防火墙拦截了

ここに画像の説明を挿入

kubectl get svc,pod   #对外暴露端口

ここに画像の説明を挿入
2. マスター ノードの IP アドレスと予約されたポートを使用して、Nginx ホームページにアクセスします。
例:

192.168.95.20:21729

ここに画像の説明を挿入

コマンドを使用してshow connection が失敗しました

kubectl describe pod nginx-77b4fdf86c-krqtk

結果を示す:

open /run/flannel/subnet.env: no such file or directory 

関連する cni ネットワーク構成ファイルが不足していることがわかりました。
メイン ノードが存在するかどうかを含め、各ノードを注意深く確認する必要があります/run/flannel/subnet.env。内容は次のようになります。

FLANNEL_NETWORK=10.244.0.0/16
FLANNEL_SUBNET=10.244.0.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true

エラー ログを確認すると、関連する cni ネットワーク構成ファイルが欠落していることがわかりました。

cni ネットワーク関連の構成ファイルを作成します。
mkdir -p /etc/cni/net.d/
cat <<EOF> /etc/cni/net.d/10-flannel.conf
{
    
    "name":"cbr0","type":"flannel","delegate": {
    
    "isDefaultGateway": true}}
EOF

ここでは、cat コマンドとリダイレクト演算子 (<<) を使用して、{"name": "cbr0", "type": "flannel", "delegate": {"isDefaultGateway": true}} を /etc/cni に書き込みます。 /net.d/10-flannel.conf ファイル。

mkdir /usr/share/oci-umount/oci-umount.d -p
mkdir /run/flannel/
cat <<EOF> /run/flannel/subnet.env
FLANNEL_NETWORK=10.199.0.0/16
FLANNEL_SUBNET=10.199.1.0/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true
EOF

ここでは、<<EOF と EOF の間に複数行のテキストがあり、各行に環境変数の定義が含まれています。具体的には:
FLANNEL_NETWORK=10.199.0.0/16 は FLANNEL_NETWORK という名前の環境変数を定義し、それを 10.199.0.0/16 に設定します。
FLANNEL_SUBNET=10.199.1.0/24 は、FLANNEL_SUBNET という名前の環境変数を定義し、それを 10.199.1.0/24 に設定します。
FLANNEL_MTU=1450 は、FLANNEL_MTU という環境変数を定義し、それを 1450 に設定します。
FLANNEL_IPMASQ=true は、FLANNEL_IPMASQ という環境変数を定義し、それを true に設定します。
ここに画像の説明を挿入
ファイルがないノードがある場合は、それをコピーして再度デプロイしてください。このエラーは報告されません。
host1 ノードで確認してみましょう。

cat /run/flannel/subnet.env
cat  /etc/cni/net.d/10-flannel.conf

ここに画像の説明を挿入
このコマンドにより、スレーブ ノードに cni ネットワークに関連する関連設定ファイルがすでに存在していることがわかります。
これらの重要な構成ファイルが欠落している場合、クラスター ログにもエラーが報告されます。

cni config uninitialized
5月 06 12:44:06 master kubelet[48391]: W0506 12:44:06.599700   48391 cni.go:213] Unable to update cni config: No networks found in /etc/cni/net.d
5月 06 12:44:07 master kubelet[48391]: E0506 12:44:07.068343   48391 kubelet.go:2170] Container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready

ここに画像の説明を挿入
上記の設定に問題がなければ、
最終的にはnginxが正常に表示されます。

Kubectl get nodes

ここでの kubectl get Nodes コマンドは、ノード名、ステータス、ロール (マスター/ワーカー) など、クラスター内のすべてのノードに関する情報を含むテーブルを返します。
ここに画像の説明を挿入
ブラウザのURL入力

192.168.95.25:30722

ここに画像の説明を挿入
または、CLUSTER-IP を入力します。

10.100.184.180

ここに画像の説明を挿入
このようにして、Kubernetes クラスターのデプロイに成功し、nginx のデプロイが完了しました。

これらはすべて個人的にテストされており、上記の通常の操作に従って正常に展開できます。
最後になりますが、夏休みはしっかりとしっかり勉強して、楽しく過ごしてください。

おすすめ

転載: blog.csdn.net/Myx74270512/article/details/132006170