通過 MicroK8s 搭建你的 K8s 環境
本文使用「署名 4.0 國際 (CC BY 4.0)」許可協議,歡迎轉載、或重新修改使用,但需要註明來源。 署名 4.0 國際 (CC BY 4.0)
本文作者: 蘇洋
建立時間: 2019年09月08日 統計字數: 15348字 閱讀時間: 31分鐘閱讀 本文連結: soulteary.com/2019/09/08/…
通過 MicroK8s 搭建你的 K8s 環境
去年的時候,我曾經寫過如何簡單搭建 Kubernetes 叢集,當時使用的是官方的工具箱:Kubeadm,這個方案對於只是想試試的同學來說,還是過於複雜。這裡介紹一款簡單的工具:MicroK8s。
官方給這款工具的人設是“無需運維的 Kubernetes ,服務於工作站、物聯網。”最大的價值在於可以快速搭建單節點的容器編排系統,用於生產試驗。
官方網站裡的檔案有簡單介紹如何安裝使用,但是卻未曾考慮安裝過程存在網路問題的神州大陸的同學們,本文將結合這種情況聊聊。
寫在前面
官方在早些時候宣佈接下來將會使用 Containerd
替換 docker
:
The upcoming release of v1.14 Kubernetes will mark the MicroK8s switch to Containerd and enhanced security. As this is a big step forward we would like to give you a heads up and offer you a preview of what is coming. Give it a test drive with:
snap install microk8s --classic --channel=1.13/edge/secure-containerd
You can read more in our blog 117,and the respective pill request 13 Please,let us know 5 how we can make this transition smoother for you. Thanks
社群裡已經有使用者諮詢/吐槽過了,這裡考慮減少變化,暫時還是以使用 docker 作為容器封裝的 1.13 ,新版本留給下一篇“折騰”吧。
使用 SNAP 安裝 MicroK8S
snap 是 **canonical ** 公司給出的更“高階”的包管理的解決方案,最早應用在 Ubuntu Phone 上。
使用 snap 安裝 K8s 確實很簡單,就像下面一樣,一條命令解決問題:
snap install microk8s --classic --channel=1.13/stable
複製程式碼
但是這條命令如果不是在海外主機上執行,應該會遇到安裝緩慢的問題。
snap install microk8s --classic --channel=1.13/stable
Download snap "microk8s" (581) from channel "1.13/stable" 0% 25.9kB/s 2h32m
複製程式碼
想要解決這個問題,暫時只能給 snap 新增代理來解決問題,snap 不會讀取系統的環境變數,只讀取應用的變數檔案。
使用下面的命令可以方便的修改 snap 的環境變數,但是預設編輯器是 ** nano **,非常難用。
systemctl edit snapd.service
複製程式碼
這裡可以先更新編輯器為我們熟悉的 ** vim **:
sudo update-alternatives --install "$(which editor)" editor "$(which vim)" 15
sudo update-alternatives --config editor
複製程式碼
互動式終端需要我們手動輸入數字,然後按下回車確認選擇:
There are 5 choices for the alternative editor (providing /usr/bin/editor).
Selection Path Priority Status
------------------------------------------------------------
* 0 /bin/nano 40 auto mode
1 /bin/ed -100 manual mode
2 /bin/nano 40 manual mode
3 /usr/bin/vim 15 manual mode
4 /usr/bin/vim.basic 30 manual mode
5 /usr/bin/vim.tiny 15 manual mode
Press <enter> to keep the current choice[*],or type selection number: 5
update-alternatives: using /usr/bin/vim.tiny to provide /usr/bin/editor (editor) in manual mode
複製程式碼
再次執行上面編輯環境變數的命令,新增一段代理配置:
[Service]
Environment="HTTP_PROXY=http://10.11.12.123:10240"
Environment="HTTPS_PROXY=http://10.11.12.123:10240"
Environment="NO_PROXY=localhost,127.0.0.1,192.168.0.0/24,*.domain.ltd"
複製程式碼
再次執行安裝,安裝進度起飛:
snap install microk8s --classic --channel=1.13/stable
Download snap "microk8s" (581) from channel "1.13/stable" 31% 14.6MB/s 11.2s
複製程式碼
如果速度沒有變化,可以考慮過載 snap 服務。
systemctl daemon-reload && systemctl restart snapd
複製程式碼
如果上面的操作一切順利,你將會看到類似下面的日誌:
snap install microk8s --classic --channel=1.13/stable
microk8s (1.13/stable) v1.13.6 from Canonical✓ installed
複製程式碼
執行列表命令,可以看到當前 snap 已經安裝好的工具:
snap list
Name Version Rev Tracking Publisher Notes
core 16-2.40 7396 stable canonical✓ core
microk8s v1.13.6 581 1.13 canonical✓ classic
複製程式碼
之前 獨立安裝 K8s 需要先安裝 docker,而使用 snap 安裝的話,這一切都是預設就緒的。
microk8s.docker version
Client:
Version: 18.09.2
API version: 1.39
Go version: go1.10.4
Git commit: 6247962
Built: Tue Feb 26 23:56:24 2019
OS/Arch: linux/amd64
Experimental: false
Server:
Engine:
Version: 18.09.2
API version: 1.39 (minimum version 1.12)
Go version: go1.10.4
Git commit: 6247962
Built: Tue Feb 12 22:47:29 2019
OS/Arch: linux/amd64
Experimental: false
複製程式碼
獲取 Kubernetes 依賴映象
想要使用 Kubernetes,除了安裝 MicroK8s 外,還需要獲取它依賴的工具映象。然而映象的獲取還是需要費點功夫的,首先獲取 1.13
版本的 MicroK8s 程式碼:
git clone --single-branch --branch=1.13 https://github.com/ubuntu/microk8s.git
複製程式碼
然後獲取其中宣告的容器映象列表:
grep -ir 'image:' * | awk '{print $3 $4}' | uniq
複製程式碼
因為官方程式碼的奔放,我們會獲得長得奇形怪狀的映象名稱:
localhost:32000/my-busybox
elasticsearch:6.5.1
alpine:3.6
docker.elastic.co/kibana/kibana-oss:6.3.2
time="2016-02-04T07:53:57.505612354Z"level=error
cdkbot/registry-$ARCH:2.6
...
...
quay.io/prometheus/prometheus
quay.io/coreos/kube-rbac-proxy:v0.4.0
k8s.gcr.io/metrics-server-$ARCH:v0.2.1
cdkbot/addon-resizer-$ARCH:1.8.1
cdkbot/microbot-$ARCH
"k8s.gcr.io/cuda-vector-add:v0.1"
nginx:latest
istio/examples-bookinfo-details-v1:1.8.0
busybox
busybox:1.28.4
複製程式碼
根據我們要部署的目標伺服器的具體需求,替換掉 $ARCH
變數,去掉無意義的映象名稱,整理好的列表如下:
k8s.gcr.io/fluentd-elasticsearch:v2.2.0
elasticsearch:6.5.1
alpine:3.6
docker.elastic.co/kibana/kibana-oss:6.3.2
cdkbot/registry-amd64:2.6
gcr.io/google_containers/defaultbackend-amd64:1.4
quay.io/kubernetes-ingress-controller/nginx-ingress-controller-amd64:0.22.0
jaegertracing/jaeger-operator:1.8.1
gcr.io/google_containers/k8s-dns-kube-dns-amd64:1.14.7
gcr.io/google_containers/k8s-dns-dnsmasq-nanny-amd64:1.14.7
gcr.io/google_containers/k8s-dns-sidecar-amd64:1.14.7
k8s.gcr.io/kubernetes-dashboard-amd64:v1.8.3
k8s.gcr.io/heapster-influxdb-amd64:v1.3.3
k8s.gcr.io/heapster-grafana-amd64:v4.4.3
k8s.gcr.io/heapster-amd64:v1.5.2
cdkbot/addon-resizer-amd64:1.8.1
cdkbot/hostpath-provisioner-amd64:latest
quay.io/coreos/k8s-prometheus-adapter-amd64:v0.3.0
grafana/grafana:5.2.4
quay.io/coreos/kube-rbac-proxy:v0.4.0
quay.io/coreos/kube-state-metrics:v1.4.0
quay.io/coreos/addon-resizer:1.0
quay.io/prometheus/prometheus
quay.io/coreos/prometheus-operator:v0.25.0
quay.io/prometheus/alertmanager
quay.io/prometheus/node-exporter:v0.16.0
quay.io/coreos/kube-rbac-proxy:v0.4.0
k8s.gcr.io/metrics-server-amd64:v0.2.1
cdkbot/addon-resizer-amd64:1.8.1
nvidia/k8s-device-plugin:1.11
cdkbot/microbot-amd64
k8s.gcr.io/cuda-vector-add:v0.1
nginx:latest
istio/examples-bookinfo-details-v1:1.8.0
istio/examples-bookinfo-ratings-v1:1.8.0
istio/examples-bookinfo-reviews-v1:1.8.0
istio/examples-bookinfo-reviews-v2:1.8.0
istio/examples-bookinfo-reviews-v3:1.8.0
istio/examples-bookinfo-productpage-v1:1.8.0
busybox
busybox:1.28.4
複製程式碼
將上面的列表儲存為 package-list.txt 在網路通暢的雲伺服器上使用下面的指令碼,可以將 K8s 依賴的工具映象離線儲存:
PACKAGES=`cat ./package-list.txt`;
for package in $PACKAGES; do docker pull "$package"; done
docker images | tail -n +2 | grep -v "<none>" | awk '{printf("%s:%s\n",$1,$2)}' | while read IMAGE; do
for package in $PACKAGES;
do
if [[ $package != *[':']* ]];then package="$package:latest"; fi
if [ $IMAGE == $package ];then
echo "[find image] $IMAGE"
filename="$(echo $IMAGE| tr ':' '-' | tr '/' '-').tar"
echo "[save as] $filename"
docker save ${IMAGE} -o $filename
fi
done
done
複製程式碼
將映象轉存待部署伺服器的方式多種多樣,這裡提一種最簡單的方案:scp
PACKAGES=`cat ./package-list.txt`;
for package in $PACKAGES;
do
if [[ $package != *[':']* ]];then package="$package:latest";fi
filename="$(echo $package| tr ':' '-' | tr '/' '-').tar"
# 根據自己實際場景修改地址
scp "mirror-server:~/images/$filename" .
scp "./$filename" "deploy-server:"
done
複製程式碼
如果順利你將看到類似下面的日誌:
k8s.gcr.io-fluentd-elasticsearch-v2.2.0.tar 100% 140MB 18.6MB/s 00:07
elasticsearch-6.5.1.tar 100% 748MB 19.4MB/s 00:38
alpine-3.6.tar 100% 4192KB 15.1MB/s 00:00
docker.elastic.co-kibana-kibana-oss-6.3.2.tar 100% 614MB 22.8MB/s 00:26
cdkbot-registry-amd64-2.6.tar 100% 144MB 16.1MB/s 00:08
gcr.io-google_containers-defaultbackend-amd64-1.4.tar 100% 4742KB 13.3MB/s 00:00
...
...
複製程式碼
最後在目標伺服器使用 docker load
命令匯入映象即可。
ls *.tar | xargs -I {} microk8s.docker load -i {}
複製程式碼
接下來可以正式安裝 K8s 啦。
正式開始安裝 Kubernetes
使用 MicroK8s 配置各種元件很簡單,只需要一條命令:
microk8s.enable dashboard dns ingress istio registry storage
複製程式碼
完整的元件列表可以通過 microk8s.enable --help
來檢視:
microk8s.enable --help
Usage: microk8s.enable ADDON...
Enable one or more ADDON included with microk8s
Example: microk8s.enable dns storage
Available addons:
dashboard
dns
fluentd
gpu
ingress
istio
jaeger
metrics-server
prometheus
registry
storage
複製程式碼
執行 enable
順利的話,你將看到類似下面的日誌:
logentry.config.istio.io/accesslog created
logentry.config.istio.io/tcpaccesslog created
rule.config.istio.io/stdio created
rule.config.istio.io/stdiotcp created
...
...
Istio is starting
Enabling the private registry
Enabling default storage class
deployment.extensions/hostpath-provisioner created
storageclass.storage.k8s.io/microk8s-hostpath created
Storage will be available soon
Applying registry manifest
namespace/container-registry created
persistentvolumeclaim/registry-claim created
deployment.extensions/registry created
service/registry created
The registry is enabled
Enabling default storage class
deployment.extensions/hostpath-provisioner unchanged
storageclass.storage.k8s.io/microk8s-hostpath unchanged
Storage will be available soon
複製程式碼
使用 microk8s.status
檢查各個元件的狀態:
microk8s is running
addons:
jaeger: disabled
fluentd: disabled
gpu: disabled
storage: enabled
registry: enabled
ingress: enabled
dns: enabled
metrics-server: disabled
prometheus: disabled
istio: enabled
dashboard: enabled
複製程式碼
但是元件就緒,不代表 K8s 已經安裝就緒,使用 microk8s.inspect
排查下安裝部署結果:
Inspecting services
Service snap.microk8s.daemon-containerd is running
Service snap.microk8s.daemon-docker is running
Service snap.microk8s.daemon-apiserver is running
Service snap.microk8s.daemon-proxy is running
Service snap.microk8s.daemon-kubelet is running
Service snap.microk8s.daemon-scheduler is running
Service snap.microk8s.daemon-controller-manager is running
Service snap.microk8s.daemon-etcd is running
Copy service arguments to the final report tarball
Inspecting AppArmor configuration
Gathering system info
Copy network configuration to the final report tarball
Copy processes list to the final report tarball
Copy snap list to the final report tarball
Inspect kubernetes cluster
WARNING: IPtables FORWARD policy is DROP. Consider enabling traffic forwarding with: sudo iptables -P FORWARD ACCEPT
複製程式碼
解決方法很簡單,使用 ufw
新增幾條規則即可:
sudo ufw allow in on cbr0 && sudo ufw allow out on cbr0
sudo ufw default allow routed
sudo iptables -P FORWARD ACCEPT
複製程式碼
再次使用 microk8s.inspect
命令檢查,會發現 WARNING 已經消失了。
但是 Kubernetes 真的安裝就緒了嗎?跟隨下一小節尋找答案吧。
解決 Kubernetes 不能正常啟動
在上面的操作順利之後完畢後,使用 microk8s.kubectl get pods
檢視當前 Kubernetes pods 狀態,如果看到 ContainerCreating
,那麼說明 Kubernetes 還需要一些額外的“修補工作”。
NAME READY STATUS RESTARTS AGE
default-http-backend-855bc7bc45-t4st8 0/1 ContainerCreating 0 16m
nginx-ingress-microk8s-controller-kgjtl 0/1 ContainerCreating 0 16m
複製程式碼
使用 microk8s.kubectl get pods --all-namespaces
檢視詳細的狀態,不出意外的話,將看到類似下面的日誌輸出:
NAMESPACE NAME READY STATUS RESTARTS AGE
container-registry registry-7fc4594d64-rrgs9 0/1 Pending 0 15m
default default-http-backend-855bc7bc45-t4st8 0/1 ContainerCreating 0 16m
default nginx-ingress-microk8s-controller-kgjtl 0/1 ContainerCreating 0 16m
...
...
複製程式碼
首要的問題就是解決掉這個處於 Pending 狀態的容器。使用 microk8s.kubectl describe pod
可以快速檢視當前這個問題 pod 的詳細狀態:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 22m default-scheduler Successfully assigned default/default-http-backend-855bc7bc45-t4st8 to ubuntu-basic-18-04
Warning FailedCreatePodSandBox 21m kubelet,ubuntu-basic-18-04 Failed create pod sandbox: rpc error: code = Unknown desc = failed pulling image "k8s.gcr.io/pause:3.1": Error response from daemon: Get https://k8s.gcr.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)
Warning FailedCreatePodSandBox 43s (x45 over 21m) kubelet,ubuntu-basic-18-04 Failed create pod sandbox: rpc error: code = Unknown desc = failed pulling image "k8s.gcr.io/pause:3.1": Error response from daemon: Get https://k8s.gcr.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)
複製程式碼
參考日誌輸出,可以發現,之前整理的依賴映象列表,還存在“漏網之魚”: MicroK8s 中還包含未寫在程式中,從遠端配置中獲取的映象。
對於這種情況,我們只能通過給 docker
新增代理來解決問題(或者手動一個一個來)。
編輯 MicroK8s 使用的 docker 環境變數配置檔案 vi /var/snap/microk8s/current/args/dockerd-env
,在其中新增代理配置,比如:
HTTPS_PROXY=http://10.11.12.123:10555
HTTPS_PROXY=http://10.11.12.123:10555
NO_PROXY=127.0.0.1
複製程式碼
接著重啟 docker :
sudo systemctl restart snap.microk8s.daemon-docker.service
複製程式碼
這一切就緒之後,執行下面的命令,重置 MicroK8s 並再次嘗試安裝各種元件:
microk8s.reset
microk8s.enable dashboard dns ingress istio registry storage
複製程式碼
命令之後完畢之後,片刻之後再次執行 microk8s.kubectl get pods
會發現所有的 pod 的狀態就都是 Running:
NAME READY STATUS RESTARTS AGE
default-http-backend-855bc7bc45-w62jd 1/1 Running 0 46s
nginx-ingress-microk8s-controller-m9lc2 1/1 Running 0 46s
複製程式碼
使用 microk8s.kubectl get pods --all-namespaces
繼續進行驗證:
NAMESPACE NAME READY STATUS RESTARTS AGE
container-registry registry-7fc4594d64-whjnl 1/1 Running 0 2m
default default-http-backend-855bc7bc45-w62jd 1/1 Running 0 2m
default nginx-ingress-microk8s-controller-m9lc2 1/1 Running 0 2m
istio-system grafana-59b8896965-xtc27 1/1 Running 0 2m
istio-system istio-citadel-856f994c58-fbc7c 1/1 Running 0 2m
istio-system istio-cleanup-secrets-9q8tw 0/1 Completed 0 2m
istio-system istio-egressgateway-5649fcf57-cbqlv 1/1 Running 0 2m
istio-system istio-galley-7665f65c9c-l7grc 1/1 Running 0 2m
istio-system istio-grafana-post-install-sl6mb 0/1 Completed 0 2m
istio-system istio-ingressgateway-6755b9bbf6-hvnld 1/1 Running 0 2m
istio-system istio-pilot-698959c67b-zts2v 2/2 Running 0 2m
istio-system istio-policy-6fcb6d655f-mx68m 2/2 Running 0 2m
istio-system istio-security-post-install-5d7bb 0/1 Completed 0 2m
istio-system istio-sidecar-injector-768c79f7bf-qvcjd 1/1 Running 0 2m
istio-system istio-telemetry-664d896cf5-jz22s 2/2 Running 0 2m
istio-system istio-tracing-6b994895fd-z8jn9 1/1 Running 0 2m
istio-system prometheus-76b7745b64-fqvn9 1/1 Running 0 2m
istio-system servicegraph-5c4485945b-spf77 1/1 Running 0 2m
kube-system heapster-v1.5.2-64874f6bc6-8ghnr 4/4 Running 0 2m
kube-system hostpath-provisioner-599db8d5fb-kxtjw 1/1 Running 0 2m
kube-system kube-dns-6ccd496668-98mvt 3/3 Running 0 2m
kube-system kubernetes-dashboard-654cfb4879-vzgk5 1/1 Running 0 2m
kube-system monitoring-influxdb-grafana-v4-6679c46745-68vn7 2/2 Running 0 2m
複製程式碼
如果你看到的結果類似上面這樣,說明 Kubernetes 是真的就緒了。
快速建立應用
安都安完了,總得試著玩玩看吧,當然,這裡不會隨大流的展示下管理後臺就匆匆擱筆。
使用 kubectl
基於現成的容器建立一個 deployment:
microk8s.kubectl create deployment microbot --image=dontrebootme/microbot:v1
複製程式碼
既然用上了最先進的編排系統,不體驗下自動擴容豈不是太可惜了:
microk8s.kubectl scale deployment microbot --replicas=2
複製程式碼
將服務暴露出來,建立流量轉發:
microk8s.kubectl expose deployment microbot --type=NodePort --port=80 --name=microbot-service
複製程式碼
使用 get
命令檢視服務狀態:
microk8s.kubectl get all
複製程式碼
如果一切順利的話,你將會看到類似下面的日誌輸出:
NAME READY STATUS RESTARTS AGE
pod/default-http-backend-855bc7bc45-w62jd 1/1 Running 0 64m
pod/microbot-7c7594fb4-dxgg7 1/1 Running 0 13m
pod/microbot-7c7594fb4-v9ztg 1/1 Running 0 13m
pod/nginx-ingress-microk8s-controller-m9lc2 1/1 Running 0 64m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/default-http-backend ClusterIP 10.152.183.13 <none> 80/TCP 64m
service/kubernetes ClusterIP 10.152.183.1 <none> 443/TCP 68m
service/microbot-service NodePort 10.152.183.15 <none> 80:31354/TCP 13m
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/nginx-ingress-microk8s-controller 1 1 1 1 1 <none> 64m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/default-http-backend 1/1 1 1 64m
deployment.apps/microbot 2/2 2 2 13m
NAME DESIRED CURRENT READY AGE
replicaset.apps/default-http-backend-855bc7bc45 1 1 1 64m
replicaset.apps/microbot-7c7594fb4 2 2 2 13m
複製程式碼
可以看到我們剛剛建立的 Service 地址是 10.11.12.234:31354
。使用瀏覽器訪問,可以看到應用已經跑起來啦。
本著“誰製造誰收拾”的綠色環保理念,除了“無腦”建立外,我們也需要學會如何治理(銷燬),使用 delete
命令,先銷燬 deployment :
microk8s.kubectl delete deployment microbot
複製程式碼
執行完畢後日誌輸出會是下面一樣:
deployment.extensions "microbot" deleted
複製程式碼
在銷燬 service 前,我們需要使用 get
命令先獲取所有的 service 的名稱:
microk8s.kubectl get services microbot-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
microbot-service NodePort 10.152.183.15 <none> 80:31354/TCP 24m
複製程式碼
得到了 service 名稱後,依舊是使用 delete
命令,刪除不需要的資源:
microk8s.kubectl delete service microbot-service
複製程式碼
執行結果如下:
service "microbot-service" deleted
複製程式碼
檢視 Dashboard
估計還是有同學會想一窺 Dashboard 的狀況。
可以通過 microk8s.config
命令,先獲得當前伺服器監聽的 IP 地址:
microk8s.config
apiVersion: v1
clusters:
- cluster:
server: http://10.11.12.234:8080
name: microk8s-cluster
contexts:
- context:
cluster: microk8s-cluster
user: admin
name: microk8s
current-context: microk8s
kind: Config
preferences: {}
users:
- name: admin
user:
username: admin
複製程式碼
可以看到,當前監聽的服務 IP 地址為 10.11.12.234
,使用 proxy
命令,開啟流量轉發:
microk8s.kubectl proxy --accept-hosts=.* --address=0.0.0.0
複製程式碼
接著訪問下面的地址,就能看到我們熟悉的 Dashboard 啦:
http://10.11.12.234:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/#!/login
複製程式碼
其他
完整安裝下來,連著系統一共花費了接近 8G 的儲存空間,所以如果你打算持續使用的話,可以提前規劃下磁碟空間,比如參考 遷移 Docker 容器儲存位置 把未來持續膨脹的 docker 映象包地址先做個遷移。
df -h
Filesystem Size Used Avail Use% Mounted on
udev 7.8G 0 7.8G 0% /dev
tmpfs 1.6G 1.7M 1.6G 1% /run
/dev/sda2 79G 16G 59G 21% /
複製程式碼
最後
這篇文章成文於一個月之前,由於使用的還是 “Docker” 方案,理論來說時效性還是靠譜的,如果你遇到了什麼問題,歡迎討論溝通。
看著草稿箱堆積越來越多的有趣內容,或許應該考慮“合作撰寫”的模式了。
—EOF
我現在有一個小小的折騰群,裡面聚集了一些喜歡折騰的小夥伴。
在不發廣告的情況下,我們在裡面會一起聊聊軟體、HomeLab、程式設計上的一些問題,也會在群裡不定期的分享一些技術沙龍的資料。
喜歡折騰的小夥伴歡迎掃碼新增好友。(請註明來源和目的,否則不會通過稽核)