說明
? 本文通過簡單的示例,帶領初學者快速邁入Docker、Kubernetes(K8S)容器世界的大門。假設,你已擁有一個K8S集群,否則,可通過minikube或minishift快速搭建一實驗環境。
Docker Docker與K8S? Docker本質上是一種虛擬化技術,類似于KVM、XEN、VMWARE,但其更輕量化,且將Docker部署在Linux環境時,其依賴于Linux容器技術(LXC)。Docker較傳統KVM等虛擬化技術的一個區別是無內核,即多個Docker虛擬機共享宿主機內核,簡而言之,可把Docker看作是無內核的虛擬機,每Docker虛擬機有自己的軟件環境,相互獨立。
? K8S與Docker之間的關系,如同Openstack之于KVM、VSphere之于VMWARE。K8S是容器集群管理系統,底層容器虛擬化可使用Docker技術,應用人員無需與底層Docker節點直接打交道,通過K8S統籌管理即可。
Docker基礎? 如下所示,運行docker run -it --name test-docker busybox /bin/sh命令,觀察其輸出,可發現docker先在本地查找名為busybox的鏡像(Image)1,若本地無鏡像,則從docker.io官方鏡像庫(Registry)下載鏡像后保存到本地,接著以此鏡像構建一個名為test-docker的虛擬機,其Docker官方術語命名為容器(Container)。
# docker run -it --name test-docker busybox /bin/sh Unable to find image "busybox:latest" locally Trying to pull repository docker.io/library/busybox ... latest: Pulling from docker.io/library/busybox f70adabe43c0: Pull complete Digest: sha256:186694df7e479d2b8bf075d9e1b1d7a884c6de60470006d572350573bfa6dcd2 / #
? Docker較傳統KVM、VMware虛擬機更輕量,如下所示,test-docker容器不會運行額外的系統與內核進程,其僅運行docker run命令提供的/bin/sh進程:
/ # ps -ef PID USER TIME COMMAND 1 root 0:00 /bin/sh 7 root 0:00 ps -ef
? 如在Openstack中創建虛擬機,首先需在Glance鏡像庫中存儲虛擬機鏡像,而后才能選擇鏡像以創建虛擬機。Docker同理,且官方提供一共享的鏡像倉庫(Registry),其中存儲了各式各樣的鏡像(Image)。如本例用busybox鏡像創建容器,其鏡像被拉(pull)到了本地,可執行如下命令檢查發現其僅1MB左右,相當輕量。
# docker images|grep busybox docker.io/busybox latest 8ac48589692a 5 weeks ago 1.146 MB
? 通過本節,我們了解了3個Docker基本要素:鏡像倉庫(Registry)中存儲了鏡像(Image),而鏡像(Image)包含了程序運行所需的軟件環境,當部署容器(Container)時,鏡像(Image)通過網絡被拉取到Doker主機(Node)。
Kubernetes? K8S是Google開源容器集群管理系統,其源于Google內部管理系統Borg,以下將通過一個個簡單連貫的示例,帶領初學者熟悉K8S集群。
Pod? K8S以Pod為最小單位來調度并管理Docker容器(Container),其中1個Pod可含多個容器,且相同Pod里的容器共享本地網絡,容器間可通過localhost地址互訪,即容器如同部署在相同的主機上,而以Pod為最小單元來調度則表明:Pod內的容器被調度到相同的Docker節點上。
? 如下所示,創建一名為myhttp的Pod,其包含一個使用httpd鏡像部署的容器,容器名為myhttp:
# cat > /tmp/myhttpd.pod <? 執行kubectl get pod命令觀察Pod運行成功后,接著驗證容器能提供web服務:
# kubectl get pod NAME READY STATUS RESTARTS AGE myhttp 1/1 Running 0 1h # kubectl describe pod myhttp|grep IP IP: 10.129.0.232 # curl 10.129.0.232DeploymentIt works!
? 將應用直接以Pod形式部署很少見,主因是:Pod無法提供彈性伸縮,且節點故障時K8S無法將其調度到幸存節點上,缺少自愈能力。鑒于此,應用常使用“鏡像(Rc)/部署(Deployment)”部署,且在K8S新版本中,官方推薦用Deployment替代Rc部署無狀態(Stateless)應用。
? 執行kubectl delete pod myhttp刪除pod后,換成以Deployment部署:
# cat > myhttp.yaml <? Deployment中的.spec.replicas表明部署多少個Pod,如本例當前僅含一Pod:
# kubectl get deploy,pod NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE deploy/myhttp 1 1 1 1 2m NAME READY STATUS RESTARTS AGE po/myhttp-7bc6d8b87c-gzlkq 1/1 Running 0 2m? 執行kubectl delete pod
刪除Pod后,可發現deployment將自動重建pod,其將確保擁有.spec.replicas個pod數量,即意味著,當pod異常時,deployment具備自愈特性。 # kubectl delete pod myhttp-7bc6d8b87c-gzlkq # kubectl get pod -w NAME READY STATUS RESTARTS AGE myhttp-7bc6d8b87c-dhmtz 0/1 ContainerCreating 0 2s myhttp-7bc6d8b87c-dhmtz 1/1 Running 0 8s myhttp-7bc6d8b87c-gzlkq 1/1 Terminating 0 8m? 當需伸縮或擴展應用時,若以Pod形式部署,則需刪除或創建Pod,而若使用Deployment部署,則我們僅需調整.spec.replicas,而后K8S鏡像控制器將自動調整Pod數量。如下所示,擴展http應用為2服務:
# kubectl scale deploy/myhttp --replicas=2 # kubectl get pod -w NAME READY STATUS RESTARTS AGE myhttp-7bc6d8b87c-cj4g8 0/1 ContainerCreating 0 3s myhttp-7bc6d8b87c-zsbcc 1/1 Running 0 8m myhttp-7bc6d8b87c-cj4g8 1/1 Running 0 18s # kubectl get deploy NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE myhttp 2 2 2 2 21m? 執行kubectl delete pod
刪除Pod后,可發現Pod名(即容器主機名)及IP是隨機分配的,那么,我們該如何訪問應用? # kubectl get pod # kubectl describe pod myhttp-7bc6d8b87c-cj4g8|grep IP IP: 10.129.3.28Service? Service服務類似于傳統的F5、A10等硬件負載均衡,但其在K8S中通過軟件實現,且當伸縮應用時可實時跟蹤后端Server,無需人為調整。
內部訪問
我們將對上節部署的myhttp應用創建一個Service服務,但在此前,先創建一個Pod作為集群內部客戶端以用于后續Service驗證。因下面驗證Svc將使用curl工具,而官方centos鏡像包含此工具,故用此鏡像創建Pod,且為保證Pod一直運行不退出,使用了command在前臺執行了無限循環命令。
# kubectl create -f - <? 執行如下命令為myhttp應用創建一個myhttp-int的服務:
# kubectl expose deployment myhttp --port=8080 --target-port=80 --name=myhttp-int service "myhttp-int" exposed? 上面命令等價于使用下面的Yaml文件手動創建Service:創建名為myhttp-int的服務,其8080端口指向后端服務的80端口,而后端服務是通過selector選擇label(標簽)為app:myhttp的Pod,觀察myhttp Deployment,可發現.spec.template.metadata.labels定義的標簽就是app:myhttp,故而,通過myhttp-int:8080即可訪問myhttp服務。
apiVersion: v1 kind: Service metadata: labels: app: myhttp name: myhttp-int spec: clusterIP: ports: - port: 8080 protocol: TCP targetPort: 80 selector: app: myhttp sessionAffinity: None? 在測試容器中通過myhttp-int:8080訪問Service,可發現將負載均衡到后端的兩pod上:
# kubectl get pod NAME READY STATUS RESTARTS AGE myclient 1/1 Running 0 1h myhttp-7bc6d8b87c-cj4g8 1/1 Running 0 1d myhttp-7bc6d8b87c-zsbcc 1/1 Running 0 1d # 重置web主頁,輸出每Pod名稱以便后續觀察 # kubectl exec myhttp-7bc6d8b87c-cj4g8 -it -- sh -c "hostname>htdocs/index.html" # kubectl exec myhttp-7bc6d8b87c-zsbcc -it -- sh -c "hostname>htdocs/index.html" # kubectl exec -it myclient -- curl myhttp-int:8080 myhttp-7bc6d8b87c-cj4g8 # kubectl exec -it myclient -- curl myhttp-int:8080 myhttp-7bc6d8b87c-zsbcc? 當伸縮Pod時,我們可通過如下命令觀察到Service將動態跟蹤后端(Endpoints)服務:
# kubectl get endpoints myhttp-int NAME ENDPOINTS AGE myhttp-int 10.129.0.237:80,10.129.3.28:80 1h # kubectl scale deploy myhttp --replicas=3 # kubectl get endpoints myhttp-int NAME ENDPOINTS AGE myhttp-int 10.129.0.237:80,10.129.3.28:80,10.131.0.194:80 1h外部訪問
? 若應用需向K8S集群外提供服務,則可創建類型為NodePort的Service,此時K8S集群上所有節點均監聽nodePort指定的端口,故外部應用可通過集群中任一節點訪問集群內部提供的服務。
# kubectl create -f - <? 執行如下命令檢查服務,發現一個為ClusterIP類型,一個為NodePort類型,但兩者均分配了ClusterIP地址:
# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE myhttp-int ClusterIP 172.30.37.438080/TCP 1h myhttp-pub NodePort 172.30.6.69 8080:30001/TCP 3m ? myhttp-pub服務通過nodePort打開了集群各節點的主機端口,此時可通過集群任何節點訪問服務:
# curl 192.168.220.21:30001 myhttp-7bc6d8b87c-zsbcc # curl 192.168.230.21:30001 myhttp-7bc6d8b87c-zsbcc # curl 192.168.240.21:30001 myhttp-7bc6d8b87c-cj4g8? 通過NodePort類型的Service雖可將服務暴露到集群外部,但問題是:端口數量有限(限制為30000-32767)、節點故障后,通過此節點訪問服務將失敗。鑒于此原因,NodePort類型的Service不常用,而是換成使用Ingress的技術來暴露服務到集群外部,但為簡單考慮,本文不再講解Ingress。
Configmap? 當容器異常時,鏡像控制器用Image重建Container,此時對容器的修改會丟失,故而,若需自定義httpd鏡像的httpd.conf文件,我們不應直接登錄各容器修改配置,而應考慮使用K8S提供的Configmap2技術,其作為中央存儲配置庫所創建的文件將Pod共享。
? 如下所示,為簡單考慮,我們隨意創建一文件并掛載到Deployment中,修改Configmap,擴展Deployment,用此來講解Configmap作用。
創建一名為my-config的cm3:
# kubectl create -f - <執行kubectl edit deploy myhttp修改Deployment,將cm掛載到/etc/myhosts目錄中。完整Yaml文件如下(PS:添加volumeMounts與volume):
apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: app: myhttp name: myhttp spec: replicas: 1 selector: matchLabels: app: myhttp template: metadata: labels: app: myhttp spec: containers: - image: httpd name: myhttp volumeMounts: - name: config-hosts mountPath: /etc/myhosts volumes: - name: config-hosts configMap: name: my-config? 修改Deploy后,可發現Pod將自動重建,而后檢查每Pod可發現目錄中含有cm的hosts文件:
# kubectl get pod NAME READY STATUS RESTARTS AGE myhttp-774ffbb989-gz6bd 1/1 Running 0 11m myhttp-774ffbb989-k8m4b 1/1 Running 0 11m myhttp-774ffbb989-t74nk 1/1 Running 0 11m # kubectl exec -it myhttp-774ffbb989-gz6bd -- ls /etc/myhosts hosts # kubectl exec -it myhttp-774ffbb989-gz6bd -- cat /etc/myhosts/hosts 127.0.0.1 localhost localhost.localdomain #::1 localhost localhost.localdomain修改cm,幾分鐘后,可發現pod中的配置被自動更新:
# kubectl edit cm my-config ... data: hosts: | 127.0.0.1 localhost localhost.localdomain ::1 localhost localhost.localdomain ... # kubectl exec -it myhttp-774ffbb989-gz6bd -- cat /etc/myhosts/hosts 127.0.0.1 localhost localhost.localdomain ::1 localhost localhost.localdomain擴展應用,繼而檢查新的Pod,發現其包含cm內容:
# kubectl scale deploy myhttp --replicas=4 # kubectl get pod myhttp-774ffbb989-gz6bd 1/1 Running 0 15h myhttp-774ffbb989-k8m4b 1/1 Running 0 15h myhttp-774ffbb989-t74nk 1/1 Running 0 15h myhttp-774ffbb989-z5d6h 1/1 Running 0 21s # kubectl exec -it myhttp-774ffbb989-z5d6h -- cat /etc/myhosts/hosts 127.0.0.1 localhost localhost.localdomain ::1 localhost localhost.localdomainSecret? 相較于Configmap用于保存明文,那么Secret則保存密文,如用戶密碼等銘感數據,可使用Secret加密保存。如下所示,我們創建一個Secret加密用戶與密碼,而后提供給容器使用。
Opaque的Secret數據是一個map類型,要求value是base64編碼格式。加密用戶與密碼:
# echo -n root | base64 cm9vdA== # echo -n Changeme | base64 Q2hhbmdlbWU=創建名為userpwd-secret的Secret,其包含用戶與密碼:
# kubectl create -f - <更新deployment,將secret以volume方式掛載到容器中:
# kubectl edit deployment myhttp ... spec: ... spec: containers: - image: httpd ... volumeMounts: - name: userpwd mountPath: /etc/mysecret ... volumes: - name: userpwd secret: secretName: userpwd-secret ...登錄容器可發現secret中的key被保存為文件,其內容為value,但在容器內已被正確解密:
# kubectl exec -it myhttp-64575c77c-kqdj9 -- ls -l /etc/mysecret lrwxrwxrwx. 1 root root 15 May 17 07:01 password -> ..data/password lrwxrwxrwx. 1 root root 15 May 17 07:01 username -> ..data/username # kubectl exec -it myhttp-64575c77c-kqdj9 -- cat /etc/mysecret/username rootStorage? 我們將web應用保存到外部存儲中,而后掛載到Pod上,這樣,無論pod是否重建亦或伸縮,我們發布的應用都不會丟失。
配置NFS存儲為簡單考慮,本例采用NFS作為共享存儲:
nfs服務器安裝軟件:
# yum install nfs-utils配置共享目錄:
# mkdir -p /exports/httpd # chmod 0777 /exports/* # chown nfsnobody:nfsnobody /exports/* # cat > /etc/exports.d/k8s.exports <配置防火墻,放行nfs端口:
# firewall-cmd --add-port=2049/tcp # firewall-cmd --permanent --add-port=2049/tcp配置Selinux以允許Docker寫數據到nfs:
# getsebool -a|grep virt_use_nfs # setsebool -P virt_use_nfs=true啟動nfs服務:
# systemctl restart nfs-config # systemctl restart nfs-server # systemctl enable nfs-serverK8S集群使用存儲K8S集群每節點安裝nfs客戶端軟件,并設置Selinux權限:
# yum install nfs-utils # setsebool -P virt_use_nfs=true創建一類型為nfs的持久化卷:PersistentVolume(PV),其指向nfs后端存儲:
# kubectl create -f - <創建一持久化卷聲明PersistentVolumeClaim(PVC)指向上一步創建的PV:
# kubectl create -f - <檢查可發現pvc/httpd綁定到pv/httpd:
# oc get pv,pvc NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM ... pv/httpd 1Gi RWX Retain Bound demo/httpd ... NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE pvc/httpd Bound httpd 1Gi RWX 53s重建deployment,添加volume與mount掛載點:
# kubectl delete deploy myhttp # kubectl create -f - <Pod生成后,檢查發現nfs目錄被掛載到容器內:
# kubectl get pod # kubectl exec -it myhttp-8699b7d498-dlzrm -- df -h Filesystem Size Used Avail Use% Mounted on ... 192.168.240.11:/exports/httpd 37G 17G 21G 44% /usr/local/apache2/htdocs ... # kubectl exec -it myhttp-8699b7d498-dlzrm -- ls htdocs # 當前目錄為空登錄任何一個容器,將web應用發布到htdocs目錄:
# kubectl exec -it myhttp-8699b7d498-dlzrm -- /bin/sh # echo "this is a test of pv" > htdocs/index.html # 容器內而后,我們刪除容器亦或擴展容器,均會發現容器中的htdocs包含所發布的應用:
# kubectl delete pod -l app=myhttp # 刪除所有myhttp pod # kubectl get pod # 等待pod重建完畢 # kubectl exec -it myhttp-8699b7d498-6q8tv -- cat htdocs/index.html this is a test of pvSatefulset? 如上面用Deplyment創建的myhttp應用,其是無狀態(stateless)的,主機名是隨機動態分配的,且所有Pod可共享掛載相同的存儲(volume),但如Kafaka、Zookeeper集群,其是有狀態的,需要主機名確定為一,且各自掛載存儲,鑒于此,K8S提供了Satefulset技術來滿足此類應用需求。
? 如下所示,我們使用nginx鏡像創建一個有狀態的集群,用此來講解Statefulset用法。
不同于Deployment,我們必須先創建一個ClusterIP: None的Service服務:
# kubectl create -f - <此Service無ClusterIP,也即我們無法直接通過此Servcie訪問后端服務。
# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE web ClusterIP None80/TCP 3s 創建名為nginx的有狀態服務,鏡像數為2,且注意ServiceName配置為上步創建的Svc:
# kubectl create -f - <觀察pod啟動,可發現pod名稱為nginx-n格式4,此名稱是固定唯一的,且可發現pod是順序啟動的,即容器nginx-n在nginx-
后啟動。 # kubectl get pod -w NAME READY STATUS RESTARTS AGE nginx-0 0/1 ContainerCreating 0 7s nginx-0 1/1 Running 0 10s nginx-1 0/1 Pending 0 0s nginx-1 0/1 Pending 0 0s nginx-1 0/1 ContainerCreating 0 1s nginx-1 1/1 Running 0 13s創建的service被statefulset用在dns上以跟蹤pod名稱:
# kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh # 如下操作均在剛創建的dns-test pod中進行: # nslookup web # 查找web服務,可發現后端有兩pod ... Name: web Address 1: 10.129.0.248 nginx-0.web.demo.svc.cluster.local Address 2: 10.131.0.200 nginx-1.web.demo.svc.cluster.local # nslookup nginx-0.web # 驗證pod名稱對應的IP地址 ... Name: nginx-0.web.demo.svc.cluster.local Address 1: 10.129.0.248 nginx-0.web.demo.svc.cluster.local # nslookup nginx-1.web ... Name: nginx-1.web.demo.svc.cluster.local Address 1: 10.131.0.200 nginx-1.web.demo.svc.cluster.local配置satefulset掛載volume:
# kubectl delete statefulset nginx # 為簡單起見,刪除以上創建的statefulset # kubectl create -f - <? 注意:在volumeClaimTemplates.spec中添加的storageClassName,其指定了名為glusterfs-raid0的存儲,這樣,當pod生成時,k8s會使用動態提供5創建PVC、PV并自動從存儲池glusterfs-raid0中動態分配volume。當然,若使用Storage一節中配置的nfs存儲,則此處需刪除storageClassName,而后手動創建存儲、pv、pvc。
檢查:
# 如下卷是k8s使用動態提供自動從glusterfs創建的: # kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE www-nginx-0 Bound pvc-4a76e4a9... 1Gi RWO glusterfs-raid0 22h www-nginx-1 Bound pvc-536e8980... 1Gi RWO glusterfs-raid0 22h # kubectl get statefulset,pod NAME DESIRED CURRENT AGE statefulsets/nginx 2 2 22h NAME READY STATUS RESTARTS AGE po/nginx-0 1/1 Running 0 22h po/nginx-1 1/1 Running 0 22h # 兩Pod掛載各自的卷: # kubectl exec -it nginx-0 -- df -h Filesystem Size Used Avail Use% Mounted on 192.168.220.21:vol_e6858... 1016M 33M 983M 4% /usr/share/nginx/html # kubectl exec -it nginx-1 -- df -h Filesystem Size Used Avail Use% Mounted on 192.168.220.21:vol_c659cc... 1016M 33M 983M 4% /usr/share/nginx/htmlNamespace? 細心的讀者會在Storage一節中看到demo/httpd,此demo就是作者所使用的Namespace/Project6。如同Openstack云計算平臺提供了多租戶用途,其每租戶可創建自己的Project(項目),K8S同樣提供多租戶功能,我們可創建不同的Namespace(命名空間),并將以上所示的Pod、Service、Configmap等限制在Namespace中。
? 剛搭建的K8S集群,默認有如下兩Namespace:
# kubectl get namespace NAME DISPLAY NAME STATUS default Active # 默認命名空間 kube-system Active # k8s自身使用的命名空間? 我們可執行如下命令創建命名空間:
# kubectl create namespace demo namespace "demo" created? 而后,執行kubectl命令時可附帶”-n
“參數。如下所示,查詢Pod: # kubectl get pod -n demo NAME READY STATUS RESTARTS AGE nginx-0 1/1 Running 0 23h nginx-1 1/1 Running 0 23h? 最后,對于Openshift平臺,我們可執行如下命令登錄到Namespace中,這樣,我們就無需每次附帶“-n
”了。 # oc project demo # oc get pod NAME READY STATUS RESTARTS AGE nginx-0 1/1 Running 0 23h nginx-1 1/1 Running 0 23h結束語? 通過本文,我們學習了Docker、K8S核心知識,我相信讀者應完全可以熟練使用K8S平臺了。
鏡像格式為: : ,若不寫image_tag,則默認為latest tag ?參考官方文檔:Configure a Pod to Use a ConfigMap。 ? 內容為key:value格式,且一個cm可包含多個 ? statefulset名稱的生成規則是固定的: -n ?存儲必須支持動態提供,如glusterfs存儲,要支持動態提供,必須配置heketi; ? Openshift平臺,其Project即為K8S的Namespace ?
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/32679.html
說明 ? 本文通過簡單的示例,帶領初學者快速邁入Docker、Kubernetes(K8S)容器世界的大門。假設,你已擁有一個K8S集群,否則,可通過minikube或minishift快速搭建一實驗環境。 Docker Docker與K8S ? Docker本質上是一種虛擬化技術,類似于KVM、XEN、VMWARE,但其更輕量化,且將Docker部署在Linux環境時,其依賴于L...
說明 ? 本文通過簡單的示例,帶領初學者快速邁入Docker、Kubernetes(K8S)容器世界的大門。假設,你已擁有一個K8S集群,否則,可通過minikube或minishift快速搭建一實驗環境。 Docker Docker與K8S ? Docker本質上是一種虛擬化技術,類似于KVM、XEN、VMWARE,但其更輕量化,且將Docker部署在Linux環境時,其依賴于L...
摘要:刪除鏡像表示強行刪除是將鏡像保存成文件加載鏡像結語是個好東西,以上僅僅是一些常用基本操作,但它就像通往新世界的大門,為大規模集群化部署提供了可能,以后有空再寫一下容器編排的文章,敬請關注 showImg(https://segmentfault.com/img/remote/1460000014989024?w=1292&h=710); 引言 相信很多技術同學在開發時都會使用虛擬機,配...
摘要:最近在舉辦的上描述,他們缺乏必要的開源社區,網絡貢獻者,以及一線開發人員和客戶,成為深受大家歡迎擁護的基礎技術。 大約18個月之前,科技出版物中充斥著容器,以及他們將如何從根本上改變企業IT的新聞。我們在這些說的天花亂墜的新市場中提取信息,匯總到容器的現狀博客。基于我們的研究,可以清楚的看到,容器的確造就了市場趨勢,而且代表了十年來的一次在企業基礎設施方面的轉型,這樣的轉型會重新塑整個...
閱讀 1858·2021-09-22 15:45
閱讀 1639·2019-08-30 15:55
閱讀 1829·2019-08-29 11:16
閱讀 3302·2019-08-26 11:44
閱讀 702·2019-08-23 17:58
閱讀 2698·2019-08-23 12:25
閱讀 1624·2019-08-22 17:15
閱讀 3597·2019-08-22 16:09