Docker 從入門到放棄

 

K8s

SVN

Subversion 官網html

http://subversion.tigris.org/前端

Openshift

案例新浪雲SAE openshift自己是一種套件java

技術 描述
Kubernutes 管理容器的組件; 集羣管理 %80內容
Apache 對外接口服務, 以及帳號的管理
Git 代碼的管理, svn
Etd 非關係型數據庫
docker 容器 hub.docker.com 國內的docker倉庫, 時速雲 hub.tenxcloud.com 阿里雲的docker

默認密碼是Asimovnode

隔離namespace的定義python

Namespace 系統調用參數 隔離內容
UTS CLONE_NEWUTS 主機名與域名
IPC CLONE_NEWIPC 信號量、消息隊列和共享內存
PID CLONE_NEWPID 進程編號
Network CLONE_NEWNET 網絡設備、網絡棧、端口等等
Mount CLONE_NEWNS 掛載點(文件系統)
User CLONE_NEWUSER 用戶和用戶組

Docker官方提供的公共鏡像倉庫public registry http://registry.hub.docker.com/ 紅帽官方提供的公共鏡像倉庫 http://registry.access.redhat.com/mysql

Doker

Docker被稱爲第三代Pass平臺 DotCloud, 主要基於Pass平臺爲開發者.linux

Docker的核心技術cgroups. 將一組程序定義爲一個group, 在這個group中, 有分配好的特定比例的cpu時間, io時間, 可用內存大小等. 最先是由google工程師提出. cgroups的重要概念是"子系統", 也就是資源控制器, 每一個子系統就是一個資源的分配器. 好比CPU子系統是控制CPU時間分配的. 首先須要掛載子系統, 而後纔有control group. 筆記: http://blog.opskumu.com/docker.htmlios

容器技術

LXC是linux containers的簡稱, 是一種基於容器的操做系統層級的虛擬化技術. 藉助於namespace的隔離機制和cgroup限額功能, LXC提供了一套統一的API和工具來創建和管理容器. Namespace: 命名空間, 至關於平行宇宙, 每一個命名空間相互隔離, 互不干擾 LXC: 提供一個共享kernel的OS級別的虛擬化方法, 在執行時不用重複加載kernel, 因爲共享kernel, 會致使一些kernel參數沒辦法在某個特定容器內進行修改. AUFS: docker的文件系統, 是一個能透明覆蓋一或多個現有文件系統的層狀文件系統. 支持將不一樣目錄掛載到同一個虛擬文件系統下, 能夠把不一樣的目錄聯合在一塊兒, 組成一個單一的目錄. 這是一種虛擬的文件系統, 文件系統不須要格式化, 直接掛載便可. 支持寫入復刻(copy on write). 差別存儲, 最大化公用底層的文件系統. 容器不建議使用sshd服務. docker exec命令能夠進入容器排查問題.nginx

Docker部署

baseurl = https://yum.dockerproject.org/repo/main/centos/7 gpgkey = https://yum.dockerproject.org/gpg

禁用firewalld, 啓動iptables 查看docker的基本信息

docker info

查看docker版本

docker version

查看容器的日誌

docker logs [containerID]

Docker配置參數

配置文件爲/etc/sysconfig/docker OPTIONS用來控制Docker Daemon進程參數 -H 表示Docker Daemon綁定的地址, -H=unix:///var/run/docker.sock -H=tcp://0.0.0.0:2375 --registry-mirror 表示Docker Registry的鏡像地址 --registry-mirror=http://4bc5abeb.m.daocloud.io --insecure-registry 表示(本地) 私有Docker Registry的地址. --insecure-registry ${privateRegistryHost}:5000 --selinux-enabled是否開啓SELinux,默認開啓 --selinux-enabled=true --bip 表示網橋docker0使用指定CIDR網絡地址, --bip=172.17.42.1 -b 表示採用已經建立好的網橋, -b=xxx

OPTIONS=-H=unix:///var/run/docker.sock -H=tcp://0.0.0.0:2375 --registrymirror=http://4bc5abeb.m.daocloud.io --selinux-enabled=true

docker的日誌默認放到/var/log/messages中 查找docker image

docker search java

docker run. docker run的命令結束了, container也就結束了

docker run [options] IMAGE[:TAG][Command][ARG...]
docker run -it java ps 

-d: 在後臺執行 docker exec 能夠進入到該容器中. 或者使用attach從新鏈接容器的會話. 若是是attach在退出的時候可能會將容器關閉 交互就使用 -i -t docker run 時 沒有指定--name, namedaemon會自動生成隨機字符串UUID docker基礎命令, create只是建立可是不會運行

docker create/start/stop/pause/unpause

建立mysql容器

docker create --name mysqlsrv1 -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 mysql
-e: 提供環境變量

而後對虛擬機訪問3306端口就能夠訪問容器的mysql服務

docker exec -it mysqlsrv1 /bin/bash

查看環境變量, docker的配置文件通常使用環境變量來定義

docker exec mysqlsrv1 env

運行中止後的容器也會佔用磁盤空間, 在一次性運行的容器後面添加 run -rm, 在執行後刪除 將容器變成鏡像

docker commit <container> [repo:tag]

docker能夠在容器中額外掛載一些目錄, 好比能夠添加一些ping的工具

docker run --privileged -v /sbin:/mnt/sbin -v /bin:/mnt

Docker命令

coreOS開發, 於redhat合做, google提供Kubernetes管理工具. images至關於一個模板, 實質就是一個歸檔包tar. 分爲readonly只讀檔和寫的層. image生成容器. 一個鏡像能夠對應多個容器, 若是容器中須要進行寫操做, 則須要從images中copy一個.

直接下載一個image

docker search rhel7
docker pull workstation.pod6.example.com:5000/library/rhel7

執行一個容器

docker run --help
docker run -itd 275be1d3d070 "/bin/bash"
-i: interactive
-t: tty
-d: 後臺運行

修改docker image標籤

docker tag docker.io/ubuntu ubuntu:mini

查看鏡像的詳細信息

docker inspect [id]

刪除鏡像

docker rmi [id]
	-f: 強制刪除

根據本地鏡像製做鏡像文件

docker commit -m "Added a new file" -a "Docker Newbee" b1624e625c32 test
	-a: 做者信息
	-m: 提交信息
	-p: 提交時暫停容器運行

模板能夠經過openvz的網站進行下載https://download.openvz.org/template/precreated/

cat centos-6-x86_64-minimal.tar.gz |docker import - centos6:latest

保存和導入鏡像, 使用save和load

docker save -o ubuntu.tar test:latest 
docker load --input ubuntu.tar
docker load < ubuntu.tar

還可使用import導入容器

docker import 
cat test_for_run.tar| docker import - test/ubuntu:v1.0

docker既可使用docker load命令來導入鏡像存儲文件到本地鏡像庫, 也可使用docker import命令來導入一個容器快照到本地鏡像倉庫. 容器快照文件將丟棄全部的歷史記錄和元數據信息, 而鏡像存儲文件將保存完整記錄, 體積比較龐大. 新建容器 docker run至關於docker create 而後在docker exec

docker create  
	-t: 提供一個僞終端pseudo-tty
	-i: 讓容器保持打開狀態
	-d: 守護狀態運行Daemonized

查看容器日誌

docker log <container id>

查看容器狀態

docker ps
	-a: 能夠查看離線的容器

鏈接容器, attach在離開容器的時候會關閉容器

docker attach
docker exec -it
docker exec -it 213c4841716d "bin/bash"

刪除容器

docker rm
	-f: 強行終止並刪除一個運行中的容器
	-l: 刪除容器的鏈接, 單保留容器
	-v: 刪除容器掛載的數據卷

製做容器包

febootstrap -i bash -i wget -i net-tools -i openssh-server -i openssh-client rhel71 rhel71doc http://content.example.com/ose3.0/x86_64/dvd/rhel-7-server-rpms/

import image到docker中

cd rhel71doc/
tar -c .|docker import - rehl71_stu6

刪除容器

docker rm

修改image標籤

docker tag dl.dockerpool.com:5000/ubuntu:latest ubuntu:latest

查看image詳細信息, 返回的是一個json格式的消息, 也可使用-f參數來指定某一個參數

docker inspect [imageid]

新建容器 docker create, 讓docker分配一個僞終端pseudo-tty

docker create -it centos:6 "/bin/bash"

啓動容器可使用docker run 或者docker start

docker run centos /bin/echo "hello world"

終止容器, 使用docker stop. 它首先向容器發送SIGTERM信號, 等待一段時間後(默認爲10s), 在發送SIGKILL信號終止容器 也能夠是用docker start

docker start [container id]

使用docker attach附着到容器中

docker attach [container id]

查看某個容器的進程pid

docker inspect --format "{{ .State.Pid }}" [container id]

使用nsenter登陸到容器中

nsenter --target 6803 --mount --uts --ipc --net --pid

使用registry鏡像建立私有倉庫

建立本地的鏡像倉庫

 
 

使用Dockerfile製做鏡像

建立鏡像的方法有三種, 基於已有鏡像建立, 基於本地模板導入以及Dockerfile建立

Dockerfile 文件配置

FROM rhel7
MAINTAINER Sun Ying 
EXPOSE 80
RUN yum -y install httpd
RUN echo "Sample welcome page" >/var/www/html/index.html
CMD /usr/sbin/httpd -DFOREGROUND

執行buildfile, 指在當前目錄下查找Dockerfile, 而且將image命名爲ying/webservice

docker build -t ying/webservice .

由於製做buildfile的時候是在容器中執行的, 咱們若是須要添加一些文件到容器中. 則須要使用ADD進行添加 ADD hello.sh /bin/hello.sh

FROM rhel7
MAINTAINER Sun Ying 
EXPOSE 80
ADD hello.sh /bin/hello.sh
ENV WORD hello world
RUN /bin/hello.sh

複雜案例: 製做ubuntu+java+tomcat+ssh server鏡像 ENTRYPOINT是告訴鏡像須要執行什麼指令, 即在鏡像被使用的時候執行的指令

FROM ubuntu
MAINTAINER Ying "ying.sun@example.com"
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" >/etc/apt/sources.list
RUN apt-get update
RUN apt-get install -y openssh-server
RUN mkidr -p /var/run/sshd
RUN echo "root:123456"|chpasswd
RUN apt-get install python-software-properties
RUN add-apt-repository ppa:webupd8team/java
RUN apt-get update
RUN apt-get install -y vim wget curl oracle-java7-installer	 tomcat7
# 設置JAVA_HOME環境變量
RUN update-alternatives --display java 
RUN echo "JAVA_HOME=/usr/lib/jvm/java-7-oracle" >> /etc/environment 
RUN echo "JAVA_HOME=/usr/lib/jvm/java-7-oracle" >> /etc/default/tomcat7
# 開啓容器的22, 8080端口
EXPOSE 22
EXPOSE 8080
# 設置tomcat7初始化運行
ENTRYPOINT service tomcat7 start && /usr/sbin/sshd -D

Supervisor能夠啓動多個進程. supervisoer做爲守護進程監管多個進程運行. 注意, docker的程序必定要放在前臺執行. superviord還會去監管他所啓動的進程

[supervisord]
nodaemon=true
[program:sshd]
command=/usr/sbin/sshd -D
[program:apache2]
command=/bin/bash -c "source /etc/apache2/envvars && exec /usr/sbin/apache2 -DFOREGROUND"

通常來講一個容器中應該只執行一個程序, docker只會監測容器中的前臺程序是否執行正常. 程序的配置文件若是放在鏡像中會對鏡像的升級帶來運維的成本. 通常來講能夠放在環境變量中(ENV). 此外, 日誌輸出也是docker的一個問題. 通常來講日誌, 須要綁定到持久存儲. 或者使用syslog 端口映射的方式, 傳輸到主機上 使用etcd/zookeepr來管理配置變動信息. 自己是key/value的架構

Docker Volume

/var/lib/docker/graph: 存放本地Image裏面的分層信息 /var/lib/docker/devicemapper/devicemapper/data: 存儲了image和container的二進制數據文件 /var/lib/docker/devicemapper/devicemapper/metadata: 存儲了相關的元數據 docker的data文件是一個稀疏磁盤空間, 默認是100G, 實際使用的大小可使用du來進行查看. 每一個容器的大小最大爲10G. aufs drvier是docker最先期支持的driver, 是linux內核的一個補丁集. ubuntu會使用 device mapper: 2.6以後引入的, 提供了一種邏輯設備到物理設備的映射框架, 是LVM2的核心. 支持塊級別的copy on write特性. VFS: 虛擬文件系統, 不支持COW btrfs: 很是快, 仍然在進化中 高頻寫操做須要volume: 大量日誌文件系統, 數據庫系統等 可使用volume

docker run -it -v /volume rhel7 /bin/bash

使用docker inspect 查看

    "Volumes": {
        "/volume": "/var/lib/docker/volumes/ec3f9aecdffc0818aaec803ca5eccb60506ce3ca4b1bc7e0676
e763733d39ad3/_data"
    },
    "VolumesRW": {
        "/volume": true
    },

也可使用本機目錄掛載到docker容器中去

docker run --rm=true -it -v /storage /volume java /bin/bash
						  本機目錄	容器目錄

volume的互聯, 基於數據容器的單主機互聯.

docker run --rm=true --privileges=true --volume-from=32dsdfadsfsf -it rhel7 /bin/bash

docker容器互聯

容器間基於link互聯, 默認狀況下docker容許container互通, 經過-icc=false關閉互通. 一旦關閉互通, 只能經過-link name:alias 命令鏈接指定container

關閉容器互聯

/usr/bin/docker daemon --icc=false --iptables=true

link的隔離是隔離容器端口的. link只能解決一臺主機之間的互聯.

docker run --rm=true --name=myjavaserver -it java /bin/bash
docker run --rm=true --link=myjavaserver:serverM1 -it java /bin/bash

多臺主機的話則沒法使用link進行互聯. SOCAT是一個多功能的網絡工具, 能夠用來作簡單的HTTP proxy

socat TCP4-LISTEN:6666 TCP4:proxy.company.com:8080

最簡單經常使用的互聯方式: 端口映射. 使用docker-proxy

宿主機的0.0.0.0:8080 --> 容器80
docker run -p "8080:80"
docker run --rm=true --name=mysqlserver -p 8066:3306 -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_USER=ying -e MYSQL_PASSWORD=nsadm -e MYSQL_DATABASE=testing workstation.pod0.example.com:5000/openshift3/mysql-55-rhel7
docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8066 -container-ip 172.17.0.5 -container-port 3306

能夠添加NAT

-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8066 -j DNAT --to-destination 172.17.0.6:3306
-A DOCKER -d 172.17.0.6/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 3306 -j ACCEPT
-A POSTROUTING -s 172.17.0.6/32 -d 172.17.0.6/32 -p tcp -m tcp --dport 3306 -j MASQUERADE

proxy每一個port的映射要使用將近11MB的內存. 所以建議直接使用宿主機網絡共享出來

docker run --rm=true --name=mysqlserver --net=host -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_USER=ying -e MYSQL_PASSWORD=nsadm -e MYSQL_DATABASE=testing workstation.pod0.example.com:5000/openshift3/mysql-55-rhel7

多個容器公用一個網絡, 下面的例子中, 第二個容器使用了第一個容器的IP 地址

docker run --rm=true --name=mysqlserver -e MYSQL_ROOT_PASSWORD=123456 mysql
docker run --rm=true --net=container:mysqlserver java ip addr 

共享一個網絡的狀況下, 互相訪問能夠經過localhost來進行彼此訪問

docker run --rm=true --net=container:mysqlserver java curl localhost:3306

基於路由的容器互聯

首先須要修改容器的IP地址, 這個時候常常會由於linux網橋自身的問題沒法成功刪除

ifconfig docker0 down 
brctl delbr docker0 

在兩臺主機上分別執行

route add -net 172.18.0.0/16 gw 172.25.0.9
route add -net 172.17.0.0.16 gw 172.25.0.11
清理防火牆規則
iptables -F; iptables -t nat -F 

網絡將會以ovs爲趨勢, docker官方提出了libnetwork的概念, 還在繼續的發展中.

namespace

平行宇宙. 在不一樣虛擬機中的網卡彼此是不可見的, tap是由軟件實現的. veth pari是用於不一樣network namespace間進行通訊的方式, veth pari將一個network namespace數據發往另外一個network namespace的veth. 查看容器真正的pid

docker inspect -f '{{.State.Pid}}' [containerID]

建立一個namespace網絡的軟連接

mkdir -p /var/run/netns
ln -s /proc/1469/ns/net /var/run/netns/1469

此後就可使用ip netns來進行操做和查看

ip netns ls

查看容器中的ip地址

ip netns exec 1469 ip addr show 

在容器中查看對端的接口

ip netns exec 1469 ethtool -S eth0

在宿主機中查看網絡的veth應該與之相對應

安裝open vSwitch

yum install openvswitch

添加網橋和gre隧道

ovs-vsctl add-br br0
ovs-vsctl   add-port   br0   gre0   --    set    Interface    gre0    type=gre options:remote_ip=172.25.0.9
brctl addif docker0 br0
ip link set dev br0 up
ip link set dev docker0 up
iptables -t nat -F;iptables -F
ip route add 172.17.0.0/16 dev docker0

ovs-vsctl show

Bridge "br0"
    Port "br0"
        Interface "br0"
            type: internal
    Port "gre0"
        Interface "gre0"
            type: gre
            options: {remote_ip="172.25.0.9"}
ovs_version: "2.3.1-git3282e51"

抓gre的包

tshark -i eth0 ip proto gre

Docker管理工具

Shipyard和cAdvisor 安裝shiptyard

OPTIONS= -H=unix:///var/run/docker.sock -H=tcp://0.0.0.0:2375
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock\ shipyard/deploy start 

默認運行在8080端口, 用戶名密碼是admin/shipyard

cAdvisor是google推出的一個主要用於檢測容器狀態的工具, 也能夠經過容器直接安裝

docker run --volume=/:/rootfs:ro --volume=/var/run:/var/run:rw --volume=/sys:/sys:ro --volume=/var/lib/docker/:/var/lib/docker:ro --publish=8082:8082 --detach=true --name=cadvisor google/cadvisor:latest --port=8082

Reigstry

Registry包含一個或多個Repository Repository包含一個或多個Image. Image使用GUID表示, 有一個或多個Tag與之關聯

Kubernetes

Docker於CoreOS的恩怨情仇 cgroup最初是由google的工程師提出來, 後來被整合到內核中. Linux容器正式業界一直關注的Google基礎設施Borg和Omega的基礎之一, 基於以前Google開源的cgroup項目. Borg是一個生產環境的經驗下的論文. linux基金會介入coreOS, google, docker等公司的糾紛, 建立了OCP項目 2015年7月22日Google正式對外發布Kubernetes v1.0 CNCF(Cloud Native Computing Foundation) 基金會

Kubernetes重要概念

Namingspace: 關聯resource. 資源隔離的一種手段, 不一樣NS中的資源不能互相訪問 Resource: 關聯Namespace和ResourceQuta. 集羣中的一種資源對象, 處於某個命名空間中. 能夠持久化存儲到Etcd中, 資源有狀態且能夠配額. Label: 關聯Resouce,Label Selector. 一個Key-value值對. Master節點: 關聯工做節點Node. K8s集羣的管理節點, 負責集羣的管理. 提供集羣資源數據訪問入口. 負責API Server進程, Controller Manager服務進程. Scheduler服務進程 Node節點: 關聯master節點. K8s中的工做節點, 啓動和管理k8s中的pod實例. 接受Master節點的管理指令, 運行着一個k8s的守護進程kubelet, 負載均衡器kube-proxy

kubectl describe node kubernetes-minion1

Pod: 關聯Node和Serivce: 一組容器的一個"單一集合", K8s中的最小任務調度單元. 一個Pod中的容器共享資源(網絡, volume) Service: 關聯多個相同Label的Pod, 是一種微服務. 具備一個虛擬訪問的地址(IP + Port). Replication Controller: 關聯多個相同Label的pod. Pod副本控制器, Volumes: 關聯Pod, 是Pod上的存儲卷.

Kubernets實踐

https://github.com/kubernetes/kubernetes/blob/release-1.0/docs/getting-started-guides/centos/centos_manual_config.md 要保證master/node節點之間能夠互相經過域名訪問 建立yum源

[virt7-testing]
name=virt7-testing
baseurl=http://cbs.centos.org/repos/virt7-testing/x86_64/os/
gpgcheck=0

安裝kubernetes

yum install kubernetes

安裝etcd, 他是kubernetes的鍵值數據庫系統

yum install etcd

配置etcd

ETCD_NAME=master
ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379,http://0.0.0.0:4001"
ETCD_ADVERTISE_CLIENT_URLS="http://master.example.com:2379,http://master.example.com:4001"

驗證狀態

etcdctl set testdir/testkey0 1
etcdctl get testdir/testkey0
etcdctl -C http://master.example.com:4001 cluster-health

公用配置/etc/kubernetes/config, 配置master信息

KUBE_MASTER="--master=http://master.pod0.example.com:8080"
KUBE_ETCD_SERVERS="--etcd_servers=http://master:4001"

在Master上面配置/etc/kubernetes/apiserver

KUBE_API_ADDRESS="--address=127.0.0.1"
KUBE_API_ADDRESS="--insecure-bind-address=0.0.0.0"
KUBE_API_PORT="--port=8080"
# KUBE_ETCD_SERVERS="--etcd_servers=http://127.0.0.1:2379"
# 默認ETCD啓動在2379端口

在master上啓動服務etcd, kube-apiserver, kube-controller-manager, kube-scheduler. etcd是k8s的數據庫系統

for SERVICES in etcd kube-apiserver kube-controller-manager kube-scheduler;do systemctl restart $SERVICES; systemctl enable $SERVICES; systemctl status $SERVICES; done

在node節點上進行配置/etc/kubernetes/kubelet

KUBELET_ADDRESS="--address=0.0.0.0"
KUBELET_PORT="--port=10250"
KUBELET_HOSTNAME="--hostname_override=node.pod0.example.com"
KUBELET_API_SERVER="--api_servers=http://master.pod0.example.com:8080"

啓動kube-proxy和kubelet服務, 爲了讓kube-proxy也能找到master須要配置config文件聲明master節點位置 在master節點上能夠檢查node節點的狀態是否註冊成功

kubectl get nodes
kubectl cluster-info

修改docker配置

OPTIONS='--selinux-enabled=disabled 

在kube-scheduler和kube-controller-manager中添加

After=etcd.service
After=kube-apiserver.service
Requires=etcd.service
Requires=kube-apiserver.service

Kubernetes的版本升級

很是簡單, 經過官網下載最新版本的二進制包kubernetes.tar.gz, 解壓縮. 中止Master和Node上的服務, 將新版的可執行文件複製到kubernetes的安裝目錄下.重啓服務 kubectl

Available Commands:
  get            Display one or many resources
  describe       Show details of a specific resource or group of resources
  create         Create a resource by filename or stdin
  replace        Replace a resource by filename or stdin.
  patch          Update field(s) of a resource by stdin.
  delete         Delete a resource by filename, stdin, resource and name, or by resources and label selector.
  namespace      SUPERCEDED: Set and view the current Kubernetes namespace
  logs           Print the logs for a container in a pod.
  rolling-update Perform a rolling update of the given ReplicationController.
  scale          Set a new size for a Replication Controller.
  exec           Execute a command in a container.
  port-forward   Forward one or more local ports to a pod.
  proxy          Run a proxy to the Kubernetes API server
  run            Run a particular image on the cluster.
  stop           Gracefully shut down a resource by name or filename.
  expose         Take a replicated application and expose it as Kubernetes Service
  label          Update the labels on a resource
  config         config modifies kubeconfig files
  cluster-info   Display cluster info
  api-versions   Print available API versions.
  version        Print the client and server version information.
  help           Help about any command

查看namespace

kubectl get namespace
kubectl describe namespace default

建立應答式文件nginx.yaml

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: master.example.com:5000/nginx
    ports:
    - containerPort: 80
kubectl create -f nginx-pod.yaml

查看events

kubectl get events

搭建本地倉庫http://www.cnblogs.com/zhenyuyaodidiao/p/6500950.html

#KUBELET_POD_INFRA_CONTAINER="--pod-infra-container-image=registry.access.redhat.com/rhel7/pod-infrastructure:latest" 

每一個Pod裏運行着一個特殊的被稱之爲Pause的容器, 其餘容器則爲業務容器. 這些業務容器共享Pause容器的網絡棧和Volume掛在卷. 官方文檔https://docs.docker.com/registry/deploying/

Kubernetes定義

測試鏡像文件在https://hub.docker.com/u/kubeguide/ Node信息同步能夠經過kube-controller-manager啓動參數 --node-sync-period 設置同步的時間週期 Node是自注冊的, 當kubelet的啓動參數中設置了--register-node爲true時, kubelet會向apiserver註冊本身. Kubelet進行自注冊的啓動參數以下: --apiservers=: apiserver的地址 --kubeconfig=: 登陸apiserver所須要憑據/證書的目錄 --cloud_provider=: 雲服務商地址, 用於獲取自身的metadata --regiter-node=: 設置爲true表示自動註冊到apiserver上 一般在容器之間要使用link的方式互聯, 可是大量的link會消耗系統資源. 經過Pod的概念能夠將多個容器組合在一個虛擬的"主機"內, 能夠實現容器之間僅須要經過localhost就能互相通訊了. 一個pod中的應用容器共享一組資源 PID命名空間: pod中的不一樣應用程序能夠看到其餘應用程序的進程ID 網絡命名空間: pod中的多個容器可以訪問同一個IP和端口範圍. IPC命名空間: Pod中的多個容器可以使用systemV IPC或POSIX消息隊列進行通訊 UTS命名空間: Pod中的多個容器共享一個主機名 volumes: 共享存儲卷, pod中的各個容器能夠訪問在pod級別定義的volumes

Pod的聲明週期是經過Replication Controller來管理的. Pod的生命週期過程包括: 經過模板進行定義, 而後分配到一個Node上運行, 在pod所含容器運行結束後Pod也結束. Pod包含四種狀態. Pending: Pod定義正確, 提交到Master Running: Pod已經被分配到某個Node上. Succeeded: Pod中全部容器都成功結束 Failed: Pod中全部容器都結束了.

Label以key/value的形式附加到各類對象上, 如Pod, service, RC, Node. Label Selector分兩種, 基於等式的(Equality-based)和基於集合的(Set-Based). 基於等式的Label Selector, name=redis-slave; env != production; 基於集合的Label Selector, name in (redis-master, redis-slave) name not in (php-frontend) Replication Controller經過Label Selector來選擇要管理的Pod RC是Kubernetes系統的核心概念, 用於定義Pod副本的數量.

Service能夠看作一組提供相同服務的pod對外訪問的接口, Service做用於那些Pod是經過Label Selector來定義的. 建立本地倉庫, 首先使用阿里加速器https://cr.console.aliyun.com/ pull registry並啓動

docker pull docker.io/registry 
docker run -d -p 5000:5000 --name=master.example.com --restart=always --privileged=true  --log-driver=none -v /home/data/registrydata:/tmp/registry registry

k8s的node節點必需要安裝一個鏡像名爲 gcr.io/google_containers/pause-amd64:3.0的鏡像. 能夠從阿里雲下載.

docker pull registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0

從新tag鏡像名稱而且上傳到本地registry上

docker tag registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0 master.ex
ample.com:5000/google_containers/pause-amd64:3.0
docker push master.example.com:5000/google_containers/pause-amd64:3.0

而後修改docker中的--insecure-registry添加master.example.com:5000

kubernetes部署示例

建立redis-master-controller.yaml

apiVersion: v1
kind: ReplicationController
metadata:
  name: redis-master
  labels:
    name: redis-master
spec:
  replicas: 1
  selector:
    name: redis-master
  template:
    metadata:
      labels:
        name: redis-master
    spec:
      containers:
      - name: master
        image: master.example.com:5000/kubeguide/redis-master
        ports:
        - containerPort: 6379

建立redis-master-service.yaml 服務, 定義開放的端口和對應的pod

apiVersion: v1
kind: Service
metadata:
  name: redis-master
  labels:
    name: redis-master
spec:
  ports:
  - port: 6379
    targetPort: 6379
  selector:
    name: redis-master

查看services

kubectl get services
NAME           CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
kubernetes     10.254.0.1       <none>        443/TCP    1d
redis-master   10.254.241.203   <none>        6379/TCP   3m

因爲cluster的IP是在建立pod以後由kubernetes自動分配的, 在其餘Pod中沒法預先知道某個Service的虛擬IP地址. Kubernets則使用Linux環境變量來傳達信息. 其餘的pod經過REDIS_MASTER_SERVICE_HOST 和 REDIS_MASTER_SERVICE_PORT來獲取相關信息. 建立slave的RC

apiVersion: v1
kind: ReplicationController
metadata:
  name: redis-slave
  labels:
    name: redis-slave
spec:
  replicas: 2
  selector:
    name: redis-slave
  template:
    metadata:
      labels:
        name: redis-slave
    spec:
      containers:
      - name: master
        image: master.example.com:5000/kubeguide/guestbook-redis-slave
        env:
        - name: GET_HOSTS_FROM
          value: env
        ports:
        - containerPort: 6379

建立redis-slave-service.yaml 服務文件

apiVersion: v1
kind: Service
metadata:
  name: redis-slave
  labels:
    name: redis-slave
spec:
  ports:
  - port: 6379
  selector:
    name: redis-master

建立fronted-controller.yaml

apiVersion: v1
kind: ReplicationController
metadata:
  name: frontend
  labels:
    name: frontend
spec:
  replicas: 3
  selector:
    name: frontend
  template:
    metadata:
      labels:
        name: frontend
    spec:
      containers:
      - name: frontend
        image: master.example.com:5000/kubeguide/guestbook-php-frontend
        env:
        - name: GET_HOSTS_FROM
          value: env
        ports:
          - containerPort: 80

配置服務文件fronted-service.yaml. type=NodePort是關鍵, 表示Node上的物理機端口提供對外訪問的能力, 須要注意的是spec.ports.NodePort的端口定義由範圍限制, 默認爲30000~32767,在此範圍以外則會失敗

apiVersion: v1
kind: Service
metadata:
  name: frontend
  labels:
    name: frontend
spec:
  type: NodePort
  ports:
  - port: 80
    nodePort: 30001
  selector:
    name: frontend
tcp    LISTEN     0      128      :::30001                :::*                   users:(("kube-proxy",pid=2831,fd=9))

系統會跟根據pod和service的關係創建相應的endpoint

kubectl get endpoints

Kubernets 對外Service

Service的ClusterIP地址相對於Pod的IP地址來講相對穩定, Service被建立時被分配一個IP地址, 在銷燬該Service以前, 這個IP地址不會發生變化. Cluster IP Range池中分配到的IP只能在內部被訪問到, 全部其餘Pod均可以無障礙的訪問到它. 可是若是這個Service做爲前端的服務, 則須要這個服務提供公共IP. Kubernetes提供兩種對外的Service的type定義. NodePort和LoadBalancer NodePort: 指定spec.type=NodePort, 並指定spec.ports.nodePort的值, 系統就會在Kubernetes集羣中的每一個Node上打開一個主機上的真實端口號, 這樣, 可以訪問Node的客戶端都能經過這個端口號訪問到內部的Service了 LoadBalancer: 若是雲服務商支持外接負載均衡器, 則能夠經過spec.type=LoadBalancer定義Service. 同時須要指定負載均衡器的IP地址. status.loadBalancer.ingress.ip設置爲146.148.47.155爲雲服務商提供的負載均衡器的IP地址.

apiVersion: v1
kind: Service
metadata: {
  "kind": "Service",
  "apiVersion": "v1",
  "metadata": {
    "name": "my-service"
    },
  "spec":{
    "type": "LoadBalancer",
    "clusterIP": "10.0.171.239",
    "selector": {
      "app": "MyApp"
    },
    "ports": [
      {
        "protocol": "TCP",
        "port": 80,
        "targetPort": 9376,
        "nodePort": 30061,
      },
    ],
  },
  "status": {
    "loadBalancer": {
      "ingress": [
        {
          "ip": "146.148.47.155"
        },
      ]
    }
  }
}

多端口服務

不少狀況下, 一個服務都須要對外暴露多個端口號. 防止產生歧義, specports 下面能夠定義名字. spec.ports.name="http"

Volume

存儲卷是Pod中可以被多個容器訪問的共享目錄. K8s中的Volume與Pod生命週期相同, 但與容器的生命週期不相關. 當容器終止或者重啓時, volume中的數據不丟失. 1) EmptyDir: 一個EmptyDir Volume是在Pod分配到Node時建立的, 從它的名稱就能夠看出, 它的初始內容爲空. 同一個Pod下的全部容器均可以讀/寫EmptyDir中的相同文件. 當Pod被移除後, EmptyDir中的數據也永久刪除. 2) hostPath: 在Pod上掛載宿主機上的文件或目錄. 在不一樣的Node上具備相同配置的Pod可能會由於宿主機上的目錄和文件不一樣而致使對Volume上的目錄和文件的訪問結果不一致. 通常要使用共享存儲 以宿主機/data爲存儲卷

spec:
  template:
    spec:
      volumes:
      - name: "persistent-storage"
        hostPat:
          path: "/data"
      containers:
        volumeMounts:
          - name: "persistent-storage"
            mountPath: "/data"

3) gcePersistentDisk: 使用google計算引擎上的永久磁盤. 此時要求虛擬機是GCE虛擬機 4) awsElasticBlockStore: 與GCE相似, 該volume是Amazon提供的AWS的EBS volume 5) nfs: 使用NFS提供共享目錄掛載到Pod中.

apiVersion: v1
kind: Pod
metadata:
  name: nfs-web
spec:
  containers:
    - name: web
      image: nginx
      ports:
        - name: web
          containerPort: 80
      volumeMounts:
        - name: nfs
          mountPath: "/usr/share/nginx/html"
  volumes:
    - name: nfs
      nfs:
        server: nfs-server.localhost
        path: "/"

6) iscsi: 使用iSCSI存儲設備上的目錄掛載到Pod中 7) glusterfs 8) rbd 9) gitRepo 10) secret: 經過tmpfs實現的, 這種volume沒法持久化 11) persistentVolumeClaim: 從PV(PersistentVolume)中申請所需的空間, PV一般是一種網絡存儲. 例如NFS, iSCSI, GCE, AWS等等

Namespace

kubernetes集羣在啓動後, 會建立一個名爲default的Namespace

kubectl get namespaces

默認Pod, RC, Service都將系統建立到"default"的Namespace中 用戶能夠根據建立新的Namespace,

apiVersion: v1
kind: Namespace
metadata:
  name: development

若是不加參數, kubectl get命令將顯示屬於"default" 命名空間的對象 --namespace 參數能夠指定某個命名空間對象 注意: Label是有嚴格的語法規則的, 可是Annotation是能夠隨意定義的 Kubelet建立Pod時, 須要經過啓動一個名爲google_containers/pause的鏡像來完成對Pod網絡的配置 我以前的實驗使用了將google_containers/pause鏡像下載到本地的方式來實現的. 須要在每一個node上進行操做, 也能夠直接在kubelet的配置文件中添加

KUBELET_ARGS="--pod_infra_container_image=master.example.com:5000/google_containers/pause:3.0"

ETCD

etcd是高可用的key/value存儲系統, 用於持久化存儲集羣中. API Server則提供了操做etcd的封裝接口API, 以REST方式提供服務, 以REST方式提供服務.

ETCD集羣

ETCD配置集羣配置/etc/etcd/etcd.conf

[member]
ETCD_NAME=etcd1
ETCD_LISTEN_PEER_URLS="http://10.0.0.1:2380" 		#集羣內部使用的IP地址
[cluster]
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://10.0.0.1:2380" 	#廣播給集羣內其餘成員的URL
ETCD_INITIAL_CLUSTER="etcd1=http://10.0.0.1:2380, etcd2=http://10.0.0.2:2380, etcd3=http://10.0.0.3:2380"
ETCD_INITIAL_CLUSTER_STATE="new"		#初始集羣狀態, new爲新建集羣
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"	#集羣名稱
ETCD_ADVERTISE_CLIENT_URLS="http://10.0.0.1:2379"		#廣播給外部客戶端使用的URL

在etcd2和etcd3加入etcd-cluster集羣的實例.

ETCD_INITIAL_CLUSTER_STATE="exsit"

查看集羣節點狀態

etcdctl cluster-health

查看集羣成員列表

etcdctl member list

以kube-apiserver爲例, 訪問etcd集羣的參數設置爲

--etcd-servers=http://10.0.0.1:4001, http://10.0.0.2:4001, http://10.0.0.3:4001

Kubernetes 核心原理

資源對象Replication Controller簡寫爲RC,而Replication Controller是指"副本控制器". 建立pod的時候最好不要越過RC直接建立Pod, 由於Replication Controller會經過RC管理Pod副本. 當Pod的重啓策略爲RestartPolicy=Always時, Replication Controller纔會管理該Pod的操做(建立, 銷燬, 重啓等) Pod實例都會經過RC裏定義的Pod模板(template)建立的. selector 指定一個name, 這個name和template中的name對應 對replciationcontroller進行擴大或縮小副本數量

kubectl scale --replicas=3 replicationcontrollers foo

ResourceQuota Controller

Kubernetes 支持三個層次的資源配額管理 1. 容器級別 2. Pod級別, 3. Namespace級別 包括的限制是Pod數量; Replication Controller數量; Service數量; ResourceQuota數量; Secret數量; PV(Persistent Volume) 數量 LimitRanger做用於Pod和Container上, ResourceQuota則做用於Namespace上

ServiceAccount Controller與Token Controller

ServiceAccount Controller與Token Controller是與安全相關的兩個控制器. Service Account Controller在Controller manager啓動時被建立.

某些特殊場景下, 例如將一個外部數據庫做爲Service的後端, 或將在另外一個集羣或Namespae中的服務做爲服務的後端. 須要建立一個不帶標籤選擇器的Service. 若是不帶標籤選擇器, 系統不會自動建立Endpoint. 此時須要手動建立一個和該Service同名的Endpoint kube-proxy爲每一個Service在本地主機上開一個端口(隨機選擇), 任何訪問該端口的鏈接都被代理到響應的一個後端pod上.

k8s支持兩種主要的模式來找到Service. 1). 一個是容器的Service環境變量. 形如{SVCNAME}_SERVICE_HOST. 例如名稱爲"redis-master"的service, 它對外暴露6379 TCP端口. 且集羣IP地址爲10.0.0.11. kubelet會爲新建的容器添加以下環境變量

REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

經過環境變量來找到Service會帶來一個很差的結果, 即任何被某個pod所訪問的Service, 必須先於該Pod被建立, 不然和這個後建立的Service相關的環境變量, 將不會被加入該Pod容器中.

容器健康檢查

Pod經過兩類探針來檢查容器的健康狀態, 一個是LivenessProbe探針. 另外一類是使用LivenessProbe探針. LivenessProbe探針有三種實現方式1) ExecAction: 在容器中執行一個命令, 返回值爲0, 代表健康 2) TCPSocketAction: 經過容器的IP地址和端口號執行TCP檢查 3) HTTPGetAction: 經過容器IP地址和端口號以及路徑調用HTTP get方法

livenessProbe:
  exec:
  	command:
  	- cat
  	- /tmp/health
  initialDelaySeconds: 15
  timeoutSeconds: 1
livenessProbe:
  httpGet:
  	path: /heathz
  	port: 8080
  initialDelaySeconds: 15
  timeoutSeconds: 1

安全機制原理

Authenication認證

CA認證在API server上配置三個參數 "--client-ca-file" "--tls-cert-file" 和 "--tls-private-key-file" kuectl 的三個參數"certificate-authority" "client-certificate" 和 "client-key". 或客戶端應用的kubeconfig配置文件中的配置項

token認證方式, 添加參數"--token_auth_file=SOMEFILE"

HTTP基本認證方式添加參數"--basic_auth_file=SOMEFILE"

Auhorization受權

在經過API訪問資源以前, 必須經過訪問策略進行校驗. 訪問策略經過API Server的啓動參數 "--authorization_mode"配置, 該參數包含如下三個值 1) AlwaysDeny 2) AlwaysAllow 3) ABAC

ABAC表示配置使用用戶配置的受權策略去管理API Server的請求. HTTP請求包含四個能被受權進程識別的屬性 1) 用戶名 2) 是不是隻讀請求 3) 訪問的屬於那一類資源, 例如Pod 4) 被訪問對象所屬的Namespace 若是選用ABAC模式, 須要經過API Server的參數選項"--authorization_policy_file=SOME_FILENAME"

# 容許用戶alice作任何事情
{"user": "alice"}
# 用戶kubelet指定讀取資源Pods
{"user": "kubelet", "resource": "pods", "readonl": true} 
# 用戶Kubelet能讀和寫資源events
{"user": "kubelet", "resource": "event"}
# 用戶bob只能讀取Namespace "myNamespace" 中的資源Pods
{"user": "bob", "resource": "pods", "readonly": true, "ns": "myNamespace"}

Admission Control 插件

SecurityContextDeny: 禁止經過API server管理配置了下列兩項配置的pod

spec.containers.securityContext.seLinuxOptions
spec>containers.securityContext.runAsUser

ResourceQuota: 資源配額. --adminssion_control=ResourceQuota, 使插件生效. 對資源對象的最大數量限制

{
    "apiVersion": "v1",
    "kind": "ResourceQuota",
    "metadata": {
        "name": "quota"
    },
    "spec": {
        "hard": {
            "memory": "1Gi",
            "cpu": "20",
            "pods": "10",
            "services": "5",
            "replicationcontrollers": "20",
            "resourcequotas": "1"
        }
    }
}

LimitRanger: 用於列舉Namespace中各資源的最小限制, 最大限制和默認值. --adminssion_control=LimitRanger

apiVersion: v1
kind: LimitRange
metadata:
  name: myLimits
spec:
  limits:
  - max:
      cpu: "2"
      memory: 1Gi
    min:
      cpu: 250m
      memory: 6Mi
    type: Pod
  - default:
      cpu: 250m
      memory: 100Mi
    max:
      cpu: "2"
      memory: 1Gi
    min:
      cpu: 250m
      memory: 6Mi
    type: Container

Secret: 私密憑據 登陸私有Registry, 第一次登陸會建立私有的用戶名密碼, 相關信息將會寫入~/.dockercfg文件中

docker login localhost:5000

Service Account: 多個Secret的集合

kubectl get serviceAccounts

集羣安全配置案例

雙向認證配置

雙向認證配置: 1) 生成根證書, API server服務端證書, 服務器端私鑰, 各組件所用的客戶端證書和客戶端私鑰 2) 修改k8s各個服務進程的啓動參數, 啓動雙向認證模式 證書目錄/var/run/kubernetes 產生私鑰

openssl genrsa -out dd_ca.key 2048

生成根證書(自簽證書)

openssl req -x509 -new -nodes -key dd_ca.key -subj "/CN=example.com" -days 5000 -out dd_ca.crt

生成API Server的服務端證書和私鑰

openssl genrsa -out dd_server.key 2048
openssl req -new -key dd_server.key -subj "/CN=master.example.com" -out dd_server.csr
openssl x509 -req -in dd_server.csr -CA dd_ca.crt -CAkey dd_ca.key -CAcreateserial -out dd_server.crt -days 5000

生成Controller Manager與Scheduler進程公用的證書和私鑰

openssl genrsa -out dd_cs_client.key 2048
openssl req -new -key dd_cs_client.key -subj "/CN=master.example.com" -out dd_cs_client.csr
openssl x509 -req -in dd_cs_client.csr -CA dd_ca.crt -CAkey dd_ca.key -CAcreateserial -out dd_cs_client.crt -days 5000

生成Kubelet所用的客戶端證書和私鑰, 假設NodeIP地址爲192.168.48.142

openssl genrsa -out dd_kubelet_client.key 2048
openssl req -new -key dd_kubelet_client.key -subj "/CN=192.168.48.142" -out dd_kubelet_client.csr
openssl x509 -req -in dd_kubelet_client.csr -CA dd_ca.crt -CAkey dd_ca.key -CAcreateserial -out dd_kubelet_client.crt -days 5000

修改API Server的啓動參數/etc/kubernetes/apiserver 並重啓apiserver

KUBE_API_ARGS="--log-dir=/var/log/kubernetes --secure-port=443 --client_ca_file=/home/cert/dd_ca.crt --tls-private-key-file=/home/cert/dd_server.key --tls-cert-file=/home/cert/dd_server.crt"

驗證api server

curl https://master.example.com:443/api/v1/nodes --cert dd_cs_client.crt --key dd_cs_client.key --cacert dd_ca.crt

修改Controller Manager的啓動參數

KUBE_CONTROLLER_MANAGER_ARGS="--log-dir=/var/log/kubernetes --service_account_private_key_file=/home/cert/dd_cs_client.key --root-ca-file=/home/cert/dd_ca.crt --master=https://master.example.com:443 --kubeconfig=/etc/kubernetes/cmkubeconfig"

建立/etc/kubernetes/cmkubeconfig文件, 配置證書相關參數而且重啓kube-controller-manager服務

apiVersion: v1
kind: Config
users:
- name: controllermanager
  user:
    client-certificate: /home/cert/dd_cs_client.crt
    client-key: /home/cert/dd_cs_client.key
clusters:
- name: local
  cluster:
    certificate-authority: /home/cert/dd_ca.crt
contexts:
- context:
    cluster: local
    user: controllermanager
  name: my-context
current-context: my-context

在每一個Node上建立/var/lib/kubelet/kubeconfig文件.

apiVersion: v1
kind: Config
users:
- name: kubelet
  user:
    client-certificate: /home/dd_kubelet_client.crt
    client-key: /home/dd_kubelet_client.key
clusters:
- name: local
  cluster:
    certificate-authority: /home/dd_ca.crt
contexts:
- context:
    cluster: local
    user: kubelet
  name: my-context
current-context: my-context

修改Kubelet的啓動參數, 以修改/etc/kubernetes/kubelet配置文件爲例

KUBELET_API_SERVER="--api_servers=https://master.example.com:443"
KUBELET_ARGS="--kubeconfig=/var/lib/kubelet/kubeconfig"

配置kube-proxy, 建立/var/lib/kubeproxy/proxykubeconfig

apiVersion: v1
kind: Config
users:
- name: kubeproxy
  user:
    client-certificate: /home/dd_kubelet_client.crt
    client-key: /home/dd_kubelet_client.key
clusters:
  - name: local
    cluster:
      certificate-authority: /home/dd_ca.crt
contexts:
  - context:
      cluster: local
      user: kubeproxy
    name: my-context
current-context: my-context

配置/etc/kubernetes/proxy並重啓kube-proxy

KUBE_PROXY_ARGS="--kubeconfig=/var/lib/kubeproxy/proxykubeconfig --master=https://master.example.com:443"

簡單認證配置

建立用戶名密碼和UID的文件/root/token_auth_file

thomas, thomas, 1
admin, admin, 2

修改API Server的配置, 重啓apiserver

KUBE_API_ARGS="--secure-port=443 --token_auth_file=/root/token_auth_file"

使用curl驗證API server

curl https://master.example.com:443/version -H "Authorization: Bearer thomas" -k

HTTP base認證

建立用戶名密碼和UID的文件/root/token_auth_file

thomas, thomas, 1
admin, admin, 2

修改API Server的配置, 重啓APIserver

KUBE_API_ARGS="--secure-port=443 --basic_auth_file=/root/basic_auth_file"

用curl驗證鏈接API Server

curl https://master.example.com:443/version --basic -u thomas:thomas -k

 

 

 

Kuberntes 網絡原理

每一個Pod都有一個獨立的IP地址, 全部的Pod都在一個能夠直接連通的, 扁平的網絡空間中. 不論這些pod是否在同一個宿主機中, 都要求它們能夠直接經過對方的IP進行訪問. 這種模式稱爲IP per Pod模型 在同一個pod中的容器能夠經過localhost來鏈接其餘容器的端口.

Linux網絡棧中引入了網絡命名空間(Network Namespace), 這些獨立的協議棧被隔離到不一樣的命名空間中. 彼此間沒法通訊. Linux的網絡命名空間內能夠有本身的路由表及獨立的Iptables/Netfilter來設置提供包轉發, NAT及IP包過濾等功能. 讓處在不一樣命名空間的網絡互相通訊, 甚至和外部的網絡進行通訊, 可使用Veth設備對. 它就像一個管道, 一端連着一個網絡命名空間的協議棧, 一端連着另外一個網絡命名空間的協議棧. 建立一個網絡命名空間

ip netns add <name>

查看命名空間中的內容

ip netns exec <name> ip addr show

若是要執行多個命令, 能夠先進入命名空間的bash, 使用exit退出命名空間

ip netns exec <name> bash 

veth設備屬於能夠轉移的設備, 便可以在不一樣命名空間中轉換. 可是不少其餘設備例如lo設備, vxlan設備, ppp設備, bridge設備等都是不能夠轉移的.

ip link set veth1 netns ns1

查看是否能夠進行轉移, 爲on則意味着不能夠進行轉移

ethtool -k docker0|grep netns
netns-local: on [fixed]

veth對, 老是以成對的方式出現的. 兩端稱爲peer 建立Veth設備對:

ip link add veth0 type veth peer name veth1

將veth1遷移到netns ying中

ip link set veth1 netns ying

爲veth0/veth1建立ip地址

ip netns exec ying ip addr add 10.1.1.1/24 dev veth1
ip addr add 10.1.1.2/24 dev veth0

啓動veth0/veth1

ip link set veth0 up
ip netns exec ying ip link set dev veth1 up

此時兩個veth peer就能夠彼此通訊了. 可使用ethtool來查看veth對的peer

ethtool -S veth0

網橋

默認MAC地址的過時時間是5min. Linux的網橋提供了這些設備之間互相轉發數據的二層設備. 於switch純二層設備不一樣, 運行着linux內核的機器自己就是一臺主機, 有多是網絡報文的目的地. 其收到的報文除了轉發和丟棄, 還可能被送到網絡協議棧的上層(網絡層), 從而被這臺主機的協議棧消化. 因此此時, 網橋便是二層設備也是一個三層設備.

Linux內核是經過一個虛擬的網橋設備(Net Device)來實現網橋的. Net Device與通常的設備不一樣, 最明顯的一個特徵是它還能夠有本身的一個IP地址.

例如一個br0橋接了兩個網卡, eth0, eth1. 對於上層協議棧而言, 只看獲得br0, 上層協議棧將報文發送給br0, 網橋設備的處理代碼判斷報文該發送給eth0仍是eth1. 反過來eth0/1接收到的報文被提交給網橋處理代碼.

Iptables/Netfilter

Linux網絡協議棧中有一組回調函數掛節點. hook鉤子函數. Netfilter負責在內核執行各類掛接的規則, 運行在內核模式中. 而iptables是在用戶模式下運行的進程, 負責協助維護內核中的Netfilter的各類規則表.

路由

Linux路由表至少包含兩個表, 當啓用策略路由的時候還會有其餘表. 一個是LOCAL一個是MAIN. LOCAL表中包含全部本地設備地址. LOCAL表示在創建網絡設備的時候自動建立的, LOCAL表用於供Linux協議棧識別本地地址, 以及進行本地各個不一樣網絡接口之間的數據轉發. 查看local表

ip route show table local type local

MAIN表用於各種網絡IP地址的轉發, 它的創建既可使用靜態配置生成, 也可使用動態路由發現協議生成. 動態路由發現協議是使用一組組播功能來發送路由發現數據, 動態交換和獲取網絡的路由信息, 並更新到路由表中. Linux下支持路由發現協議的開源軟件有不少, 經常使用的有Quagga, Zebra等. 路由表查看

ip route list

Docker的網絡實現

標準的Docker支持一下四類網絡模式 host模式: 使用 --net=host指定 container模式: 使用 --net=container: NAME_or_ID指定 none模式: 使用 --net=none指定 bridge模式: 使用 --net=bridge指定

Kubernets管理模式下, 一般只會使用bridge模式. bridge模式下, 會建立一個docker0的網橋, 而後私有網絡空間會給這個網橋設置一個子網. 每個被建立出來的容器, 都會建立一個虛擬的以太網設備(veth對), 另外一端使用Linux的網絡命名空間技術, 映射到容器內的eth0設備, 而後從網橋的地址段內給eth0接口分配一個IP地址. 這樣作的結果是, 同一個主機上的容器能夠彼此通訊, 經過IP地址. 可是不一樣主機上的容器就必須使用port來實現.而後經過這個端口路由或代理到容器上. docker的iptables在nat表中. 前兩條是框架, 生效時都會走DOCKER鏈. NAT第三條的含義是, 若本地發出的數據包不是通往docker0接口時, 即發往主機以外的設備時, 都須要進行動態地址修改MASQUERADE

-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE

filter表中, 第二條若是接收到的數據包屬於之前已經創建好的鏈接, 那麼容許直接經過. 這樣接受到的數據包天然又走回docker0, 並中專到響應的容器.

-A FORWARD -j DOCKER-ISOLATION
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT

建立registry容器後, 自動添加一條路由規則

-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 5000 -j MASQUERADE
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 5000 -j ACCEPT

Kubernetes的網絡實現

1) 緊密耦合的容器到容器之間的直接通訊 同一個pod內的容器共享同一個網絡命名空間, 共享同一個Linux協議棧. 容器之間能夠用localhost地址訪問彼此的端口. 能夠直接使用本地IPC進行通訊.

2) 抽象的Pod到Pod之間的通訊 同一個Node內的Pod之間的通訊, 自己兩個pod都是在docker0網段內的IP地址. 直接經過docker0網橋進行中轉便可. 不一樣Node上的Pod之間的通訊. k8s對docker0的IP地址進行規劃, 保證每個Node上的docker0地址沒有衝突. k8s的網絡加強開源軟件Flunnel就可以管理資源池的分配. 在GCE環境下, 路由打通是經過GCE來完成的. 在私有云環境中, 須要額外的網絡配置.

3) Pod到Service之間的通訊 Service是對一組Pod的抽象. 將Service落實的是kube-proxy服務進程. 這個進程能夠看作Service的透明代理兼. kube-proxy都會在本地Node上創建一個SocketServer來負責接收請求, 而後均勻發送到後端某個Pod的端口上, 這個過程使用Round Robin負載均衡算法. Services的ClusterIP與NodePort等概念是kube-porxy經過iptables的NAT轉換實現的. 訪問Service的請求, 不管是cluster IP + TargetPort 的方式, 仍是使用節點機 Node IP + NodePort的方式, 都被節點機的iptables規則重定向到kube-proxy監聽服務代理端口. 目前kube-proxy只支持RR和會話保持(若是Service中定義了會話親和性) SocketServer: 端口是隨機選擇的一個本地空閒端口 kube-proxy在啓動和監聽到Service或Endpoint的變化後, 會在本機iptables的NAT表中添加4條規則鏈. KUBE-PORTALS-CONTAINER: 從容器中經過Service Cluster IP和端口號訪問Service請求 KUBE-PORTALS-HOST: 從主機中經過Service Cluster IP 和端口號訪問Service請求 KUBE-NODEPORT-CONTAINER: 從容器中經過Service的NodePort端口號訪問Service的請求 KUBE-NODEPORT-HOST: 從容器中經過Service的NodePort端口號訪問Service的請求

4) 集羣外部與內部組件之間的通訊 Pod被外部訪問, 經過NodePort在Node節點上打開一個對外的端口. LoadBalancer.

開源的網絡組件

直接路由

可使用靜態路由的方式, 可是靜態路由須要每臺節點都進行配置. 在每一個Node的路由表中添加到對方docker0的路由轉發規則配置項. eg. Pod1 docker0 IP子網爲10.1.10.0, Node地址爲192.168.1.128; Pod2 所在docker0網橋的IP子網是10.1.20.0, Node地址爲192.168.1.129 在Node1上用route add命令添加一條到Node2上docker0的靜態路由規則

route add -net 10.1.20.0 netmask 255.255.255.0 gw 192.168.1.129

一樣, 在Node2上添加一條到Node1上docker0的靜態路由規則

route add -net 10.1.10.0 netmask 255.255.255.0 gw 192.168.1.128

使用這種方式, 要保證每臺node的docker網段不能重疊. 若是使用在大規模場景中, 則須要添加數以百計的靜態路由. 能夠藉助動態路由發現協議工具Quagga軟件來實現.

docker pull index.alauda.cn/georce/router

Quagga容器啓動時須要以--privileged特權模式運行, 而且指定--net-host, 表示直接使用物理機的網路

docker run -itd --name-router --privileged --net=host index.alauda.cn/georce/router

過段時間後就會自動添加路由規則

Flannel

它能夠協助k8s, 給每個Node上的Docker容器分配互相不衝突的IP地址. 能在這些IP地址之間創建一個疊加網絡. Flannel建立一個flannel0的網橋, 並且這個網橋一端鏈接docker0網橋, 另外一端鏈接flanneld的服務進程. flanneld進程首先鏈接etcd, 利用etcd來管理可分配的IP地址段資源. 同時監控etcd中的每一個Pod的實際地址, 並在內存中創建一個Pod節點路由表. 而後下連docker0和物理網絡, 使用內存中的Pod節點路由表, 將docker0發給它的數據包包裝起來, 利用物理網絡的鏈接將數據包投遞到目標flanneld上, 從而完成pod到pod的直接通訊. Flannel使用了集中的etcd存儲, 集中分配和處理IP, 就不會產生衝突. Flannel經過修改docker的啓動參數將分配給它的地址段傳遞進去 --bip. 可是Flannel模型缺省地使用了UDP做爲底層傳輸協議, UDP自己並不可靠.

安裝flannel

下載地址 https://github.com/coreos/flannel/releases 將下載的壓縮包解壓, 把二進制文件flanneld和mk-docker-opts.sh複製到/usr/bin, 既完成了對flannel的安裝 編輯服務配置文件/usr/lib/systemd/system/flanneld.service

[Unit]
Description=Flanneld overlay address etcd agent 
After=network.target
Before=docker.service
[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/flanneld
EnviornmentFile=-/etc/sysconfig/docker-network
ExecStart=/usr/bin/flanneld -etcd-endpoints=${FLANNEL_ETCD} $FLANNEL_OPTIONS
[Install]
RequiredBy=docker.service
WantedBy=multi-user.target

編輯文件/etc/sysconfig/flannel,

FLANNEL_ETCD="http://192.168.48.141:4001"
FLANNEL_ETCD_KEY="/atomic.io/network"

在啓動flannel以前, 須要在etcd中添加一條網絡配置記錄, 這個配置將用於flannel分配給每一個Docker的虛擬IP地址段

etcdctl set /atomic.io/network/config '{"Network": "10.1.0.0/16"}'

flannel將覆蓋docker0網橋, 因此要先中止docker服務. 啓動flanneld服務 在每一個Node節點上執行如下命令來完成對docker0網橋的配置

/usr/libexec/flannel/mk-docker-opts.sh -i
source /run/flannel/subnet.env
ifconfig docker0 ${FLANNEL_SUBNET}

這樣便完成flanne疊加網絡的設置

Open vSwitch

Open vSwitch的網橋能夠直接創建多種通訊通道. 例如GRE/VxLAN. 當容器內的應用訪問另外一個容器的地址時, 數據包會經過容器內的默認路由發送給docker0網橋. ovs的網橋做爲docker0網橋的端口存在, 它會將數據發送給ovs網橋. ovs網絡已經經過配置創建了和其餘ovs網橋和GRE/VxLAN隧道, 天然可以將數據送達對端的Node 正常啓動Open Vswitch後, 會啓動兩個進程: ovsdb-server與ovs-vswitchd 建立網橋和GRE隧道: 在每一個Node上創建ovs的網橋br0, 而後在網橋上建立一個GRE隧道鏈接對端網橋, 最後把ovs的網橋br0做爲一個端口鏈接到docker0這個linux網橋上(能夠理解爲交換機互聯). 如此一來, 兩個節點機器上的docker0網段就能互通了. 建立ovs網橋

ovs-vsctl add-br br0

建立GRE隧道鏈接對端, remote_ip爲對端eth0的網卡地址. remote_ip要設置爲對端的IP地址

ovs-vsctl add-port br0 gre1 -- set interface gre1 type=gre option:remote_ip=192.168.18.128

添加br0到本地docker0, 使得容器流量經過OVS流經tunnel

brctl addif docker0 br0

啓動br0與docker0網橋

ip link set dev br0 up
ip link set dev docker0 up

添加路由規則, 因爲兩臺Node的docker0網段分別爲172.17.43.0/24與172.17.42.0/24, 這兩個網段的路由都要通過本機的docker0網橋路由. 須要在每一個Node上添加經過docker0網橋轉發的172.17.0.0/16端的路由規則. 至關於設置一個大的路由網段涵蓋全部Node節點

ip route add 172.17.0.0/16 dev docker0

ping test須要, 清空docker自帶iptables規則

iptables -t nat -F; iptables -F

這種網絡架構的缺點很是明顯, N個Node須要創建N*(N-1)條GRE隧道

k8s網絡案例

在k8s上啓動一個容器時, 會先啓動google_containers/pause這個容器, 其餘pod中的容器都會關聯到這個容器上. 這個pause的容器執行端口映射規則, 這樣就能夠簡化端口映射的過程. 使用docker port能夠查看端口映射

docker port <container>

添加service以後, 全部通往以及從cluster IP出來的流量都被指向kube-proxy的某個隨機端口(若是沒有設置端口映射) kube-proxy做爲一個全功能的代理服務器管理了兩個獨立的TCP鏈接: 一個是從容器到kube-proxy,另外一個是kube-proxy到負載均衡的目標pod.

K8s開發指南

傳統的Web應用大可能是B/S架構, 涉及以下規範. 1) 客戶-服務器, 這種規範的提出, 改善了用戶接口跨多個平臺的可移植性. 分離的用戶接口和數據存儲.使得不一樣的用戶共享相同的數據成爲可能 2) 無狀態性: 每次request必須包含全部的信息, 可見性: 服務端和客戶端不須要了解request的歷史. 可靠性: 減小了服務器從局部錯誤中恢復的任務量. 可伸縮性: 能夠很容易的釋放資源(無需會話保持). 無狀態性會增長每次單獨請求的開銷 3) 緩存: 客戶端緩存response數據的功能, 這樣就能夠爲之後的request公用緩存數據. 可是可能會致使服務器與客戶端的數據不一致性.

在傳統的B/S架構下, REST提供了統一接口, 分層系統和按需代碼. 1) 統一接口: 在REST世界中, 全部的事務都被抽象爲資源. REST經過通用的連接器接口對資源進行操做, 極大的解耦. 2) 分層系統: 各分層系統提升了各類層次之間的獨立性. 爲系統複雜度提供邊界. 3) 按需代碼: 容許對客戶端功能進行擴展.

REST提出了一下的設計準則 1) 網絡上的全部事物都被抽象爲資源(Resource) 2) 每一個資源對應一個惟一的資源標識符(Resource Identifier) 3) 經過通用的連接器接口(Generic Connector Interface)對資源進行操做 4) 對資源的各類操做不會改變資源標識符 5) 全部的操做都是無狀態的(Stateless)

API

GET: 獲取某一類型的資源列表 POST: 建立一個資源 GET: 得到單個資源 DELETE: 刪除單個資源 PUT: 更新或建立資源 PATCH: 選擇修改資源詳細指定的域

K8s運維管理

Node的隔離:

在kind:node 下添加spec: unschedulable: true

kubectl replace -f unschedule_node.yaml

Node的擴容:

新的Node完成安裝後向Master註冊便可

Pod動態擴容和縮放

可使用scale rc的方式來進行操做

kubectl scale rc redis-slave --replicas=3

更新資源對象的Label

能夠直接使用kubectl的label指令

kubectl label pod redis-master-bobr0 role=backend

查看響應的Label

kubectl get pods -Lrole

刪除一個Label只須要在label後加一個減號便可

kubectl label pod redis-master-bobr0 role-

修改一個Label的值, 須要加上--overwrite參數:

kubectl label pod redis-master-bobr0 role=master --overwrite

將Pod調度到指定Node上

首先給Node打一個特定的標籤

kubectl label nodes <node-name> <label-key>=<label-value>
kubectl label nodes node1 zone=north

在spec.template.spec.nodeSelector.zone: north 而後使用create -f 建立 node 而後查看node的分佈狀態

kubectl get pods -o wide

滾動升級

使用rolling-update命令, 建立一個新的RC, 而後自動控制舊的RC中的Pod副本數量逐漸減小到0.可是新舊的RC必須處在同一個Namespace中.

kubectl rolling-update redis-master -f redis-master-controller-v2.yaml

或者也能夠直接指定鏡像源進行滾動升級

kubectl rolling-update redis-master --image=redis-master:2.0

若是更新過程當中發現配置有誤, 能夠終端操做, 並使用--rollback選項進行回滾

kubectl rolling-update redis-master --image=kubeguide/redis-master:2.0 --rollback

Kubernetes的高可用方案

etcd要配置集羣參考etcd的配置部分

Kubernetes Master組件的高可用方案

至少使用三臺服務器安裝Master服務, 而且使用Active-Standby-Standby模式, 保證任什麼時候候總有一套Master可以正常工做. 可使用pacemaker等工具來實現.

k8s資源配額管理

--adminssion_control=LimiRanger, ResourceQuota spec.template.spec.name.resources.limits.cpu:0.5 spec.template.spec.name.resources.limits.memory: 128Mi cpu的0.5也能夠寫成500m. k8s啓動一個容器時, 會將CPU的配置值乘以1024並轉爲整數傳遞給docker run的--cpu-shares參數, 之因此乘以1024是由於Docker的cpu-shares參數是以1024爲技術計算CPU時間的. 這個計數僅僅是一個權重, 使用cpu時間等於權重*(全部容器權重和) 再乘以CPU核心數.

apiVersion: v1
kind: LimitRange
metadata:
  name: limit-range-1
spec:
  limits:
    - type: "Pod"
      max:
        cpu: "2"
        memory: 1Gi
      min:
        cpu: 250m
        memory: 32Mi
    - type: "Container"
      max:
        cpu: "2"
        memory: 1Gi
      min:
        cpu: 250m
        memory: 32Mi
      default:
        cpu: 250m
        memory: 64Mi

查看限額配置

kubectl describe limits

使用ResourceQuota能夠實現基於租戶的配額管理. 不一樣租戶使用不一樣的命名空間 建立命名空間

apiVersion: v1
kind: Namespace
metadata:
  name: development

建立ResourceQuota配額配置

apiVersion: v1
kind: ResourceQuota
metadata:
  name: quota-development
  namespace: development
spec:
  hard:
    cpu: "32"
    memory: 256Gi
    persistentvolumeclaims: "10"
    pods: "100"
    replicationcontrollers: "50"
    resourcequotas: "1"
    secrets: "20"
    services: "50"

查看ResourceQuota的詳細信息

kubectl describe quota quota-development --namespace=development

能夠理解爲ResouceQuota是對總額的限定, Limitrange是對個別單位的限定

K8s監控

cAdvisor

開源軟件cAdvisor用於監控容器運行狀態的利器. 默認已經安裝在kubelet組件中了, kubelet的啓動參數 --cadviosr-port定義了cAdvisor對外提供服務的端口號, 默認爲4194 cAdvisor也可使用api來獲取主機的相關信息 http://master.example.com:4194/api/v1.3/machine 返回一個json文件 查看容器節點上最近一分鐘內的性能數據

http://192.168.48.142:4194/api/v1.3/subcontainers/system.slice/docker-443bcc6793b26150bffd8ab00ac803309f0581b8470c7214763e10b40c08350f.scope

容器日誌

能夠查看資源的event日誌. 使用kubectl describe pods 能夠看到event信息, 對於troubleshooting很是有效 查看日誌內容可使用kubectl logs

kubectl logs <pod_name>
kubectl logs <pod_name> -c <container_name>

至關於容器的命令

docker logs <container_id>

查看kubernetes的系統日誌

journalctl -u kube-controller-manager

單獨指定日誌存放目錄: --log-dir=/var/log/kubernetes

K8s社區

https://github.com/GoogleCloudPlatform/kubernetes/wiki/User-FAQ https://github.com/GoogleCloudPlatform/kubernetes/wiki/Debugging-FAQ

kubernetes DNS服務配置

Pod在訪問其餘Service時, 能夠經過兩種服務發現方式來完成, 即環境變量和DNS的方式. 使用環境變量是有限制條件的, 即Service必須在Pod以前被建立出來, 而後系統才能在新建的Pod中自動設置與Service相關的環境變量. DNS則沒有這個限制

K8s提供的DNS由如下三個組件組成 1) etcd: DNS存儲 2) kube2sky: 將Kubernetes Master中的Service註冊到etcd 3) skyDNS: 提供DNS域名解析服務 這三個組件均可以以Pod的方式啓動和運行, 在K8s集羣中須要將各個Pod之間的網絡打通. 建立skydns-rc2.yaml

apiVersion: v1
kind: ReplicationController
metadata:
  name: kube-dns-v6
  namespace: default
  labels:
    k8s-app: kube-dns
    version: v6
    kubernetes.io/cluster-service: "true"
spec:
  replicas: 1
  selector:
    k8s-app: kube-dns
    version: v6
  template:
    metadata:
      labels:
        k8s-app: kube-dns
        version: v6
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - name: etcd
        image: gcr.io/google_containers/etcd:2.0.9
        command:
        - /usr/local/bin/etcd
        - -listen-client-urls
        - http://0.0.0.0:2379,http://0.0.0.0:4001
        - -advertise-client-urls
        - http://127.0.0.1:2379,http://127.0.0.1:4001
        - -initial-cluster-token
        - skydns-etcd
      - name: kube2sky
        image: gcr.io/google_containers/kube2sky:1.11
        resources:
          limits:
            cpu: 100m
            memory: 50Mi
        command:
        - /kube2sky
        - --kube_master_url=http://10.8.65.48:8080
        - -domain=kube.local
      - name: skydns
        image: gcr.io/google_containers/skydns:2015-03-11-001
        resources:
        command:
        - /skydns
        - -machines=http://localhost:4001
        - -addr=0.0.0.0:53
        - -domain=kube.local.
        ports:
        - containerPort: 53
          name: dns
          protocol: UDP
        - containerPort: 53
          name: dns-tcp
          protocol: TCP
      dnsPolicy: Default

建立skydns-svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: kube-dns
  namespace: default
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: "KubeDNS"
spec:
  selector:
    k8s-app: kube-dns
  clusterIP: 10.254.0.10
  ports:
  - name: dns
    port: 53
    protocol: UDP
  - name: dns-tcp
    port: 53
    protocol: TCP

啓動DNSserver的cluster啓動ip是10.254.0.10 修改每臺Node上Kubelet的啓動參數 --cluster_dns=10.254.0.10 --cluster_domain=cluster.local

kube2sky容器應用經過調用kubernetes master的API得到集羣中全部Service的信息, 並持續監控新Service的生成. 而後寫入etcd中

kubectl exec kube-dns-v6-5tpm2 -c etcd --namespace=default etcdctl ls /skydns/kube/local

在域名下kube.local的域名下能夠查看各服務對應的鍵值, 能夠看到完整的域名解析redis-master.default.kube.local

kubectl exec kube-dns-v6-5tpm2 -c etcd --namespace=kube-system etcdctl get /skydns/local/kube/local/default/redis-master

在每一個Pod中的/etc/resolv.conf文件中添加了DNS域名解析

nameserver 10.254.0.10
search default.svc.kube.local svc.kube.local kube.local localdomain

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

test

相關文章
相關標籤/搜索