剛開始接觸 Kubernetes 時,你學到的第一件事就是每個 Pod 都有一個唯一的 IP 和主機名,并且在同一個 Pod 中,容器可以通過 localhost 相互通信。所以,顯而易見,一個 Pod 就像一個微型的服務(wù)器。
但是,過段時間,你會發(fā)現(xiàn) Pod 中的每個容器都有一個隔離的文件系統(tǒng),并且從一個容器內(nèi)部,你看不到在同一 Pod 的其他容器中運行的進程。好吧!也許 Pod 不是一個微型的服務(wù)器,而只是一組具有共享網(wǎng)絡(luò)堆棧的容器。
但隨后你會了解到,Pod 中的容器可以通過共享內(nèi)存進行通信!所以,在容器之間,網(wǎng)絡(luò)命名空間不是唯一可以共享的東西……
基于最后的發(fā)現(xiàn),所以,我決定深入了解:
Pod 是如何在底層實現(xiàn)的
Pod 和 Container 之間的實際區(qū)別是什么
如何使用 Docker 創(chuàng)建 Pod
在此過程中,我希望它能幫助我鞏固我的 Linux、Docker 和 Kubernetes 技能。
探索 Container
OCI 運行時規(guī)范并不將容器實現(xiàn)僅限于 Linux 容器,即使用 namespace 和 cgroup 實現(xiàn)的容器。但是,除非另有明確說明,否則本文中的容器一詞指的是這種相當傳統(tǒng)的形式。
設(shè)置實驗環(huán)境(playground)
在了解構(gòu)成容器的 namespace 和 cgroups 之前,讓我們快速設(shè)置一個實驗環(huán)境:
$ cat > Vagrantfile <
最后讓我們啟動一個容器:
$ docker run --name foo --rm -d --memory='512MB' --cpus='0.5' nginx
探索容器的 namespace
首先我們來看一下,當容器啟動后,哪些隔離原語(primitives)被創(chuàng)建了:
# Look up the container in the process tree. $ ps auxf USER PID ... COMMAND ... root 4707 /usr/bin/containerd-shim-runc-v2 -namespace moby -id cc9466b3e... root 4727 \_ nginx: master process nginx -g daemon off; systemd+ 4781 \_ nginx: worker process systemd+ 4782 \_ nginx: worker process # Find the namespaces used by 4727 process. $ sudo lsns NS TYPE NPROCS PID USER COMMAND ... 4026532157 mnt 3 4727 root nginx: master process nginx -g daemon off; 4026532158 uts 3 4727 root nginx: master process nginx -g daemon off; 4026532159 ipc 3 4727 root nginx: master process nginx -g daemon off; 4026532160 pid 3 4727 root nginx: master process nginx -g daemon off; 4026532162 net 3 4727 root nginx: master process nginx -g daemon off;
我們可以看到用于隔離以上容器的命名空間是以下這些:
mnt(掛載):#容器有一個隔離的掛載表。 uts(Unix 時間共享):#容器擁有自己的 hostname 和 domain。 ipc(進程間通信):#容器內(nèi)的進程可以通過系統(tǒng)級 IPC 和同一容器內(nèi)的其他進程進行通信。 pid(進程 ID):#容器內(nèi)的進程只能看到在同一容器內(nèi)或擁有相同的 PID 命名空間的其他進程。 net(網(wǎng)絡(luò)):#容器擁有自己的網(wǎng)絡(luò)堆棧。
注意,用戶(user)命名空間沒有被使用,OCI 運行時規(guī)范提及了對用戶命名空間的支持。不過,雖然 Docker 可以將此命名空間用于其容器,但由于固有的限制,它默認情況下沒有使用。因此,容器中的 root 用戶很可能是主機系統(tǒng)中的 root 用戶。
另一個沒有出現(xiàn)在這里的命名空間是 cgroup。我花了一段時間才理解 cgroup 命名空間與 cgroups 機制(mechanism)的不同。Cgroup 命名空間僅提供一個容器的 cgroup 層次結(jié)構(gòu)的孤立視圖。同樣,Docker 也支持將容器放入私有 cgroup 命名空間,但默認情況下沒有這么做。
探索容器的 cgroups
Linux 命名空間可以讓容器中的進程認為自己是在一個專用的機器上運行。但是,看不到別的進程并不意味著不會受到其他進程的影響。一些耗資源的進程可能會意外的過多消耗宿主機上面共享的資源。
這時候就需要 cgroups 的幫助!
可以通過檢查 cgroup 虛擬文件系統(tǒng)中的相應子樹來查看給定進程的 cgroups 限制。Cgroupfs 通常被掛在 /sys/fs/cgroup 目錄,并且進程特定相關(guān)的部分可以在 /proc//cgroup 中查看:
PID=$(docker inspect --format '{{.State.Pid}}' foo) # Check cgroupfs node for the container main process (4727). $ cat /proc/${PID}/cgroup 11:freezer:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0 10:blkio:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0 9:rdma:/ 8:pids:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0 7:devices:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0 6:cpuset:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0 5:cpu,cpuacct:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0 4:memory:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0 3:net_cls,net_prio:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0 2:perf_event:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0 1:name=systemd:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0 0::/system.slice/containerd.service
似乎 Docker 使用 /docker/模式。好吧,不管怎樣:
ID=$(docker inspect --format '{{.Id}}' foo) # Check the memory limit. $ cat /sys/fs/cgroup/memory/docker/${ID}/memory.limit_in_bytes 536870912 # Yay! It's the 512MB we requested! # See the CPU limits. ls /sys/fs/cgroup/cpu/docker/${ID}
有趣的是在不明確設(shè)置任何資源限制的情況下啟動容器都會配置一個 cgroup。實際中我沒有檢查過,但我的猜測是默認情況下,CPU 和 RAM 消耗不受限制,Cgroups 可能用來限制從容器內(nèi)部對某些設(shè)備的訪問。
這是我在調(diào)查后腦海中呈現(xiàn)的容器:
探索 Pod
現(xiàn)在,讓我們來看看 Kubernetes Pod。與容器一樣,Pod 的實現(xiàn)可以在不同的 CRI 運行時(runtime)之間變化。例如,當 Kata 容器被用來作為一個支持的運行時類時,某些 Pod 可以就是真實的虛擬機了!并且正如預期的那樣,基于 VM 的 Pod 與傳統(tǒng) Linux 容器實現(xiàn)的 Pod 在實現(xiàn)和功能方面會有所不同。
為了保持容器和 Pod 之間公平比較,我們會在使用 ContainerD/Runc 運行時的 Kubernetes 集群上進行探索。這也是 Docker 在底層運行容器的機制。
設(shè)置實驗環(huán)境(playground)
這次我們使用基于 VirtualBox driver 和 Containd 運行時的 minikube 來設(shè)置實驗環(huán)境。要快速安裝 minikube 和 kubectl,我們可以使用 Alex Ellis 編寫的 arkade 工具:
# Install arkade () $ curl -sLS https://get.arkade.dev | sh $ arkade get kubectl minikube $ minikube start --driver virtualbox --container-runtime containerd
實驗的 Pod,可以按照下面的方式設(shè)置:
$ kubectl --context=minikube apply -f - <
探索 Pod 的容器
實際的 Pod 檢查應在 Kubernetes 集群節(jié)點上進行:
$ minikube ssh
讓我們看看那里 Pod 的進程:
$ ps auxf USER PID ... COMMAND ... root 4947 \_ containerd-shim -namespace k8s.io -workdir /mnt/sda1/var/lib/containerd/... root 4966 \_ /pause root 4981 \_ containerd-shim -namespace k8s.io -workdir /mnt/sda1/var/lib/containerd/... root 5001 \_ /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent root 5016 \_ /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent root 5018 \_ containerd-shim -namespace k8s.io -workdir /mnt/sda1/var/lib/containerd/... 100 5035 \_ /bin/sleep 3650d
基于運行的時間,上述三個進程組很有可能是在 Pod 啟動期間創(chuàng)建。這很有意思,因為在清單文件中,只有兩個容器,httpbin 和 sleep。
可以使用名為 ctr 的 ContainerD 命令行來交叉檢查上述的發(fā)現(xiàn):
$ sudo ctr --namespace=k8s.io containers ls CONTAINER IMAGE RUNTIME ... 097d4fe8a7002 docker.io/curlimages/curl@sha256:1a220 io.containerd.runtime.v1.linux ... dfb1cd29ab750 docker.io/kennethreitz/httpbin:latest io.containerd.runtime.v1.linux ... f0e87a9330466 k8s.gcr.io/pause:3.1 io.containerd.runtime.v1.linux
的確是三個容器被創(chuàng)建了。同時,使用另一個和 CRI 運行時監(jiān)控的命令行 crictl 檢測發(fā)現(xiàn),僅僅只有兩個容器:
$ sudo crictl ps CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID 097d4fe8a7002 bcb0c26a91c90 About an hour ago Running sidecar 0 f0e87a9330466 dfb1cd29ab750 b138b9264903f About an hour ago Running app 0 f0e87a9330466
但是注意,上述的 POD ID 字段和 ctr 輸出的 pause:3.1 容器 id 一致。好吧,看上去這個 Pod 是一個輔助容器。所以,它有什么用呢?
我還沒有注意到在 OCI 運行時規(guī)范中有和 Pod 相對應的東西。因此,當我對 Kubernetes API 規(guī)范提供的信息不滿意時,我通常直接進入 Kubernetes Container Runtime 接口(CRI)Protobuf 文件中查找相應的信息:
// kubelet expects any compatible container runtime // to implement the following gRPC methods: service RuntimeService { ... rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse) {} rpc StopPodSandbox(StopPodSandboxRequest) returns (StopPodSandboxResponse) {} rpc RemovePodSandbox(RemovePodSandboxRequest) returns (RemovePodSandboxResponse) {} rpc PodSandboxStatus(PodSandboxStatusRequest) returns (PodSandboxStatusResponse) {} rpc ListPodSandbox(ListPodSandboxRequest) returns (ListPodSandboxResponse) {} rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse) {} rpc StartContainer(StartContainerRequest) returns (StartContainerResponse) {} rpc StopContainer(StopContainerRequest) returns (StopContainerResponse) {} rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse) {} rpc ListContainers(ListContainersRequest) returns (ListContainersResponse) {} rpc ContainerStatus(ContainerStatusRequest) returns (ContainerStatusResponse) {} rpc UpdateContainerResources(UpdateContainerResourcesRequest) returns (UpdateContainerResourcesResponse) {} rpc ReopenContainerLog(ReopenContainerLogRequest) returns (ReopenContainerLogResponse) {} // ... } message CreateContainerRequest { // ID of the PodSandbox in which the container should be created. string pod_sandbox_id = 1; // Config of the container. ContainerConfig config = 2; // Config of the PodSandbox. This is the same config that was passed // to RunPodSandboxRequest to create the PodSandbox. It is passed again // here just for easy reference. The PodSandboxConfig is immutable and // remains the same throughout the lifetime of the pod. PodSandboxConfig sandbox_config = 3; }
所以,Pod 實際上就是由沙盒以及在沙盒中運行的容器組成的。沙盒管理 Pod 中所有容器的常用資源,pause 容器會在 RunPodSandbox() 調(diào)用中被啟動。簡單的互聯(lián)網(wǎng)搜索就發(fā)現(xiàn)了該容器僅僅是一個 idle 進程。
探索 Pod 的命名空間
下面就是集群節(jié)點上的命名空間:
$ sudo lsns NS TYPE NPROCS PID USER COMMAND 4026532614 net 4 4966 root /pause 4026532715 mnt 1 4966 root /pause 4026532716 uts 4 4966 root /pause 4026532717 ipc 4 4966 root /pause 4026532718 pid 1 4966 root /pause 4026532719 mnt 2 5001 root /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent 4026532720 pid 2 5001 root /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent 4026532721 mnt 1 5035 100 /bin/sleep 3650d 4026532722 pid 1 5035 100 /bin/sleep 3650d
前面第一部分很像 Docker 容器,pause 容器有五個命名空間:net、mnt、uts、ipc 以及 pid。但是很明顯,httpbin 和 sleep 容器僅僅有兩個命名空間:mnt 和 pid。這是怎么回事?
事實證明,lsns 不是檢查進程名稱空間的最佳工具。相反,要檢查某個進程使用的命名空間,可以參考 /proc/${pid}/ns 位置:
# httpbin container sudo ls -l /proc/5001/ns ... lrwxrwxrwx 1 root root 0 Oct 24 14:05 ipc -> 'ipc:[4026532717]' lrwxrwxrwx 1 root root 0 Oct 24 14:05 mnt -> 'mnt:[4026532719]' lrwxrwxrwx 1 root root 0 Oct 24 14:05 net -> 'net:[4026532614]' lrwxrwxrwx 1 root root 0 Oct 24 14:05 pid -> 'pid:[4026532720]' lrwxrwxrwx 1 root root 0 Oct 24 14:05 uts -> 'uts:[4026532716]' # sleep container sudo ls -l /proc/5035/ns ... lrwxrwxrwx 1 100 101 0 Oct 24 14:05 ipc -> 'ipc:[4026532717]' lrwxrwxrwx 1 100 101 0 Oct 24 14:05 mnt -> 'mnt:[4026532721]' lrwxrwxrwx 1 100 101 0 Oct 24 14:05 net -> 'net:[4026532614]' lrwxrwxrwx 1 100 101 0 Oct 24 14:05 pid -> 'pid:[4026532722]' lrwxrwxrwx 1 100 101 0 Oct 24 14:05 uts -> 'uts:[4026532716]'
雖然不太容易去注意到,但 httpbin 和 sleep 容器實際上重用了 pause 容器的 net、uts 和 ipc 命名空間!
我們可以用 crictl 交叉檢測驗證:
# Inspect httpbin container. $ sudo crictl inspect dfb1cd29ab750 { ... "namespaces": [ { "type": "pid" }, { "type": "ipc", "path": "/proc/4966/ns/ipc" }, { "type": "uts", "path": "/proc/4966/ns/uts" }, { "type": "mount" }, { "type": "network", "path": "/proc/4966/ns/net" } ], ... } # Inspect sleep container. $ sudo crictl inspect 097d4fe8a7002 ...
我認為上述發(fā)現(xiàn)完美的解釋了同一個 Pod 中容器具有的能力:
能夠互相通信
通過 localhost 和/或
使用 IPC(共享內(nèi)存,消息隊列等)
共享 domain 和 hostname
然而,在看過所有這些命名空間如何在容器之間自由重用之后,我開始懷疑默認邊界可以被打破。實際上,在對 Pod API 規(guī)范的更深入閱讀后發(fā)現(xiàn),將 shareProcessNamespace 標志設(shè)置為 true 時,Pod 的容器將擁有四個通用命名空間,而不是默認的三個。但是有一個更令人震驚的發(fā)現(xiàn) hostIPC、hostNetwork 和 hostPID 標志可以使容器使用相應主機的命名空間。
有趣的是,CRI API 規(guī)范似乎更加靈活。至少在語法上,它允許將 net、pid 和 ipc 命名空間限定為 CONTAINER、POD 或 NODE。因此,可以構(gòu)建一個 Pod 使其容器無法通過 localhost 相互通信 。
探索 Pod 的 cgroups
Pod 的 cgroups 是什么樣的?systemd-cgls 可以很好地可視化 cgroups 層次結(jié)構(gòu):
$ sudo systemd-cgls Control group /: -.slice ├─kubepods │ ├─burstable │ │ ├─pod4a8d5c3e-3821-4727-9d20-965febbccfbb │ │ │ ├─f0e87a93304666766ab139d52f10ff2b8d4a1e6060fc18f74f28e2cb000da8b2 │ │ │ │ └─4966 /pause │ │ │ ├─dfb1cd29ab750064ae89613cb28963353c3360c2df913995af582aebcc4e85d8 │ │ │ │ ├─5001 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent │ │ │ │ └─5016 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent │ │ │ └─097d4fe8a7002d69d6c78899dcf6731d313ce8067ae3f736f252f387582e55ad │ │ │ └─5035 /bin/sleep 3650d ...
所以,Pod 本身有一個父節(jié)點(Node),每個容器也可以單獨調(diào)整。這符合我的預期,因為在 Pod 清單中,可以為 Pod 中的每個容器單獨設(shè)置資源限制。
此刻,我腦海中的 Pod 看起來是這樣的:
利用 Docker 實現(xiàn) Pod
如果 Pod 的底層實現(xiàn)是一組具有共同 cgroup 父級的半融合(emi-fused)容器,是否可以使用 Docker 生產(chǎn)類似 Pod 的構(gòu)造?
最近我嘗試做了一些類似的事情來讓多個容器監(jiān)聽同一個套接字,我知道 Docker 可以通過 docker run —network container:語法來創(chuàng)建一個可以使用已存在的網(wǎng)絡(luò)命名空間容器。但我也知道 OCI 運行時規(guī)范只定義了 create 和 start 命令。
因此,當你使用 docker exec在現(xiàn)有容器中執(zhí)行命令時,實際上是在運行(即 create 然后 start)一個全新的容器,該容器恰好重用了目標容器的所有命名空間。這讓我非常有信心可以使用標準 Docker 命令生成 Pod。
我們可以使用僅僅安裝了 Docker 的機器作為實驗環(huán)境。但是這里我會使用一個額外的包來簡化使用 cgroups:
$ sudo apt-get install cgroup-tools
首先,讓我們配置一個父 cgroup 條目。為了簡潔起見,我將僅使用 CPU 和內(nèi)存控制器:
sudo cgcreate -g cpu,memory:/pod-foo # Check if the corresponding folders were created: ls -l /sys/fs/cgroup/cpu/pod-foo/ ls -l /sys/fs/cgroup/memory/pod-foo/
然后我們創(chuàng)建一個沙盒容器:
$ docker run -d --rm --name foo_sandbox --cgroup-parent /pod-foo --ipc 'shareable' alpine sleep infinity
最后,讓我們啟動重用沙盒容器命名空間的實際容器:
# app (httpbin) $ docker run -d --rm --name app --cgroup-parent /pod-foo --network container:foo_sandbox --ipc container:foo_sandbox kennethreitz/httpbin # sidecar (sleep) $ docker run -d --rm --name sidecar --cgroup-parent /pod-foo --network container:foo_sandbox --ipc container:foo_sandbox curlimages/curl sleep 365d
你注意到我省略了哪個命名空間嗎?沒錯,我不能在容器之間共享 uts 命名空間。似乎目前在 docker run 命令中沒法實現(xiàn)。嗯,是有點遺憾。但是除開 uts 命名空間之外,它是成功的!
cgroups 看上去很像 Kubernetes 創(chuàng)建的:
$ sudo systemd-cgls memory Controller memory; Control group /: ├─pod-foo │ ├─488d76cade5422b57ab59116f422d8483d435a8449ceda0c9a1888ea774acac7 │ │ ├─27865 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent │ │ └─27880 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent │ ├─9166a87f9a96a954b10ec012104366da9f1f6680387ef423ee197c61d37f39d7 │ │ └─27977 sleep 365d │ └─c7b0ec46b16b52c5e1c447b77d67d44d16d78f9a3f93eaeb3a86aa95e08e28b6 │ └─27743 sleep infinity ···
全局命名空間列表看上去也很相似:
$ sudo lsns NS TYPE NPROCS PID USER COMMAND ... 4026532157 mnt 1 27743 root sleep infinity 4026532158 uts 1 27743 root sleep infinity 4026532159 ipc 4 27743 root sleep infinity 4026532160 pid 1 27743 root sleep infinity 4026532162 net 4 27743 root sleep infinity 4026532218 mnt 2 27865 root /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent 4026532219 uts 2 27865 root /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent 4026532220 pid 2 27865 root /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent 4026532221 mnt 1 27977 _apt sleep 365d 4026532222 uts 1 27977 _apt sleep 365d 4026532223 pid 1 27977 _apt sleep 365d
httpbin 和 sidecar 容器看上去共享了 ipc 和 net 命名空間:
# app container $ sudo ls -l /proc/27865/ns lrwxrwxrwx 1 root root 0 Oct 28 07:56 ipc -> 'ipc:[4026532159]' lrwxrwxrwx 1 root root 0 Oct 28 07:56 mnt -> 'mnt:[4026532218]' lrwxrwxrwx 1 root root 0 Oct 28 07:56 net -> 'net:[4026532162]' lrwxrwxrwx 1 root root 0 Oct 28 07:56 pid -> 'pid:[4026532220]' lrwxrwxrwx 1 root root 0 Oct 28 07:56 uts -> 'uts:[4026532219]' # sidecar container $ sudo ls -l /proc/27977/ns lrwxrwxrwx 1 _apt systemd-journal 0 Oct 28 07:56 ipc -> 'ipc:[4026532159]' lrwxrwxrwx 1 _apt systemd-journal 0 Oct 28 07:56 mnt -> 'mnt:[4026532221]' lrwxrwxrwx 1 _apt systemd-journal 0 Oct 28 07:56 net -> 'net:[4026532162]' lrwxrwxrwx 1 _apt systemd-journal 0 Oct 28 07:56 pid -> 'pid:[4026532223]' lrwxrwxrwx 1 _apt systemd-journal 0 Oct 28 07:56 uts -> 'uts:[4026532222]'
總結(jié)
Container 和 Pod 是相似的。在底層,它們主要依賴 Linux 命名空間和 cgroup。但是,Pod 不僅僅是一組容器。Pod 是一個自給自足的高級構(gòu)造。所有 Pod 的容器都運行在同一臺機器(集群節(jié)點)上,它們的生命周期是同步的,并且通過削弱隔離性來簡化容器間的通信。這使得 Pod 更接近于傳統(tǒng)的 VM,帶回了熟悉的部署模式,如 sidecar 或反向代理。
審核編輯:劉清
-
RAM
+關(guān)注
關(guān)注
8文章
1354瀏覽量
114444 -
Linux系統(tǒng)
+關(guān)注
關(guān)注
4文章
590瀏覽量
27317 -
虛擬機
+關(guān)注
關(guān)注
1文章
904瀏覽量
28018 -
CRI
+關(guān)注
關(guān)注
1文章
16瀏覽量
12222 -
內(nèi)存控制器
+關(guān)注
關(guān)注
0文章
38瀏覽量
8875
原文標題:解決你的困惑,K8S Pod與容器到底啥區(qū)別?
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論