1 從零開始搭建Kubernetes集羣(一、開篇)
閒話
其實,搭建一個Kubernetes(K8S)集羣不是一件容易的事情,主要困難有兩個:
那一道厚厚的牆
對K8S的知識不熟悉
只要能解決上面兩個問題,搭建的過程實際上就沒有那麼複雜了。
概要
本文將從“零”開始,分享一下如何通過虛擬機,一步一步搭建一個多節點的K8S 1.10.0 集羣。
由於我個人的喜好,這裏使用的是Oracle的VirtualBox,下載地址:
https://www.virtualbox.org/wiki/Downloads
對於VirtualBox的安裝和使用,本文不詳細介紹,建議不熟悉的朋友可以提前學習體驗,免費而且非常好用。
本文的目的是搭建一個由三個節點(即三個虛擬機實例)構成的K8S集羣,其中:
虛擬機的操作系統鏡像爲:CentOS-7-x86_64-DVD-1708,下載地址:https://mirrors.tuna.tsinghua.edu.cn/centos/7.4.1708/isos/x86_64/
Kubernetes 使用的主版本爲1.10.0,將在第二章中詳細介紹
走你
爲了大家更順利的搭建,建議使用和本文相同的環境和版本。準備好VirtualBox和CentOS7鏡像後,我們即可開啓漫漫的搭建之路。本次搭建過程分爲如下章節:
從零開始搭建Kubernetes集羣(四、搭建K8S Dashboard)
從零開始搭建Kubernetes集羣(五、搭建K8S Ingress)
從零開始搭建Kubernetes集羣(六、在K8S上部署Redis集羣)
從零開始搭建Kubernetes集羣(七、如何監控K8S集羣的日誌)
廢話
本人水平有限,難免有錯誤或遺漏之處,望大家指正和諒解,歡迎評論留言。
2 從零開始搭建Kubernetes集羣(二、搭建虛擬機環境)
一、前言
在第一篇文章 從零開始搭建Kubernetes集羣(一、開篇)中,我們已經安裝好了VirtualBox,並且下載好了CentOS 7的安裝鏡像。
我們總共需要安裝三個虛擬機節點,這裏我們只安裝一個節點,另外兩個節點通過VirtualBox的複製功能實現,可以大幅度減少工作量。
好了,我們現在開始虛擬機環境搭建之旅。
二、虛擬機環境要求
操作系統 CentOS 7.4
內存 2G 【至少】
CPU 2核【至少】
硬盤 20G 【至少】
三、創建虛擬機
打開VirtualBox主界面,如下圖所示,點擊新建:
輸入虛擬機名稱,注意類型和版本正確:
設置內存爲2048M(切記至少2G):
保持默認選項:
文件位置一定要選擇一個空間足夠的分區,並且虛擬硬盤大小一定至少20GB:
四、安裝操作系統
虛擬機創建完畢後,如下圖所示,點擊設置:
點擊“存儲”,選擇“控制器:IDE”,並且點擊下方的紅框,點擊添加虛擬光驅:
選擇之前下載好的CentOS 7 安裝鏡像:
添加完畢後,如下圖所:
啓動虛擬機,自動進入如下界面,選擇“Install CentOS 7”
語言選擇中文:
這裏比較重要,“軟件選擇”不要選擇“最小安裝”,建議選擇最後一個“開發及生產工作站”。安裝位置選擇默認自動分區,禁用Kdump,打開網絡,讓你的虛擬機可以連接到互聯網:
點擊“開始安裝”,並設置一個Root密碼 ,隨後去喝一杯咖啡吧:
錯誤!未指定文件名。
image.png
咖啡喝完後,如下圖所示,重啓完成安裝:
錯誤!未指定文件名。
image.png
五、設置環境
當然,剛安裝完畢後的CentOS 7,我們需要額外進行一些配置,才能更方便的進行後續的操作。這裏,我們所有的操作使用Root賬號。
配置終端
如果不安裝VirtualBox的增強功能,直接在VirtualBox裏操作bash是一件非常噁心的事情。這裏,我也不建議安裝增強功能,因爲經常會安裝失敗,浪費時間,並且我們基本不會在CentOS的圖形界面操作。因此,一個好的辦法是,使用第三方的終端模擬軟件,如Xshell(強烈建議)、Securecrt等。
本文以Xshell爲例,設置步驟如下:
由於VirtualBox 默認使用NAT網絡轉換,宿主機無法直接訪問虛擬機,但我們只要簡單的在NAT網卡上添加端口轉發,即可訪問虛擬機。這裏,我們通過端口轉發暴露虛擬機的SSH端口(22),就可以遠程連接到虛擬機。
在設置中,選擇“網絡”=>“網卡1”>=“高級”>=“端口轉發”:
2.添加一個規則,這裏使用真實的物理機端口9000來映射到虛擬機的22端口:
在我們真實的物理機上,可以利用Xshell,通過端口9000連接到虛擬機終端上。打開Xshell,新建一個連接。注意,因爲端口是映射到宿主機上的,所以主機地址要填寫爲127.0.0.1:
使用Xshell登錄:
關閉圖形界面
CentOS 7 安裝好後,登錄時默認啓用了很佔資源的圖形界面,若啓動三個虛擬機更會卡的飛起。因此,我們可以通過如下命令切換默認的登錄方式:
命令模式
systemctl set-defaultmulti-user.target
圖形模式
systemctl set-defaultgraphical.target
這裏,強烈建議切換爲命令模式,所有的操作都通過Xshell進行足以。注意,上面的命令執行後重啓生效。
配置yum源
不建議使用CentOS 7 自帶的yum源,因爲安裝軟件和依賴時會非常慢甚至超時失敗。這裏,我們使用阿里雲的源予以替換,執行如下命令,替換文件/etc/yum.repos.d/CentOS-Base.repo:
wget -O/etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
yum makecache
關閉防火牆
防火牆一定要提前關閉,否則在後續安裝K8S集羣的時候是個trouble maker。執行下面語句關閉,並禁用開機啓動:
[root@localhost ~]#systemctl stop firewalld & systemctl disable firewalld
[1] 10341
Removed symlink/etc/systemd/system/multi-user.target.wants/firewalld.service.
Removed symlink/etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.
關閉Swap
類似ElasticSearch集羣,在安裝K8S集羣時,Linux的Swap內存交換機制是一定要關閉的,否則會因爲內存交換而影響性能以及穩定性。這裏,我們可以提前進行設置:
執行swapoff-a可臨時關閉,但系統重啓後恢復
編輯/etc/fstab,註釋掉包含swap的那一行即可,重啓後可永久關閉,如下所示:
/dev/mapper/centos-root/ xfs defaults 0 0
UUID=20ca01ff-c5eb-47bc-99a0-6527b8cb246e/boot xfs defaults 0 0
#/dev/mapper/centos-swap swap
或直接執行:sed -i '/swap / s/^/#/' /etc/fstab
關閉成功後,使用top命令查看,如下圖所示表示正常:
image.png
六、安裝Docker
當然,安裝K8S必須要先安裝Docker。這裏,我們使用yum方式安裝Docker社區最新版。Docker官方文檔是最好的教材:
https://docs.docker.com/install/linux/docker-ce/centos/#prerequisites
但由於方教授的防火牆,文檔網站經常無法查看,並且使用yum安裝也經常會超時失敗。我們使用如下方式解決:
添加倉庫
添加阿里雲的Docker倉庫:
yum-config-manager--add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum makecache
安裝Docker
執行以下命令,安裝最新版Docker:
yum install docker-ce-y
安裝成功後,如下圖所示:
運行docker --version,可以看到安裝了截止目前最新的18.03.1版本:
[root@localhost ~]#docker --version
Docker version18.03.1-ce, build 9ee9f40
啓動Docker
啓動Docker服務並激活開機啓動:
systemctl start docker& systemctl enable docker
運行一條命令驗證一下:
docker run hello-world
提示如下,則表示你的Docker安裝成功了:
3 從零開始搭建Kubernetes集羣(三、搭建K8S集羣)
一、前言
在上一篇文章 從零開始搭建Kubernetes 1.10.0 集羣(二、搭建虛擬機環境)中,我們已經搭建好了基礎的虛擬機環境。現在,我們可以開啓我們真正的K8S之旅。
我們將現有的虛擬機稱之爲Node1,用作主節點。爲了減少工作量,在Node1安裝Kubernetes後,我們利用VirtualBox的虛擬機複製功能,複製出兩個完全一樣的虛擬機作爲工作節點。三者角色爲:
Node1:Master
Node2:Woker
Node3:Woker
二、安裝Kubernetes
還是那句話,官方文檔永遠是最好的參考資料:https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/
但是,僅供參考,因爲牆的原因,並不完全適用於我們天朝子民。下面將詳細介紹在Node1上安裝Kubernetes的過程,安裝完畢後,再進行虛擬機的複製出Node2、Node3即可。
配置K8S的yum源
官方倉庫無法使用,建議使用阿里源的倉庫,執行以下命令添加kubernetes.repo
倉庫:
cat <<EOF >
/etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http:
//mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=
1
gpgcheck=
0
repo_gpgcheck=
0
gpgkey=http:
//mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
http:
//mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
關閉swap、防火牆
上一篇文章已介紹關閉
關閉SeLinux
執行:setenforce 0
安裝K8S組件
執行以下命令安裝kubelet、kubeadm、kubectl:
yum install -y kubelet kubeadm kubectl
如下圖所示:
image.png
配置kubelet的cgroup drive
確保docker的cgroup drive 和kubelet的cgroup drive一樣:
docker info | grep -i cgroup
cat /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
若顯示不一樣,則執行:
sed -i
"s/cgroup-driver=systemd/cgroup-driver=cgroupfs/g" /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
systemctl daemon-reload
如圖:
image.png
啓動kubelet
注意,根據官方文檔描述,安裝kubelet、kubeadm、kubectl三者後,要求啓動kubelet:systemctl enable kubelet && systemctl start kubelet
但實際測試發現,無法啓動,報如下錯誤:
image.png
查看日誌發現是沒有證書:
unable to load client CA file /etc/kubernetes/pki/ca.crt: open/etc/kubernetes/pki/ca.crt: no such file
or directory
image.png
我在網上沒有找到解決方法,但無意測試中發現,後面的kubeadm init
操作會創建證書。也就是說,現在無法啓動並不影響後續操作,繼續!
下載K8S的Docker鏡像
本文使用的是K8S官方提供的kubeadm工具來初始化K8S集羣,而初始化操作kubeadm init
會默認去訪問谷歌的服務器,以下載集羣所依賴的Docker鏡像,因此也會超時失敗,你懂得。
但是,只要我們可以提前導入這些鏡像,kubeadm init
操作就會發現這些鏡像已經存在,就不會再去訪問谷歌。網上有一些方法可以獲得這些鏡像,如利用Docker Hub製作鏡像等,但稍顯繁瑣。
這裏,我已將初始化時用到的所有Docker鏡像整理好了,鏡像版本是V1.10.0。推薦大家使用。
地址:https://pan.baidu.com/s/11AheivJxFzc4X6Q5_qCw8A
密碼:2zov
準備好的鏡像如下圖所示:
image.png
K8S更新速度很快,截止目前最新版本是V1.10.2。本人提供的鏡像會越來越舊,需要最新版本的讀者可以根據網上教材自行製作
腳本docker_images_load.sh
用於導入鏡像:
dockerload <
quay.io#calico#node.tar
dockerload <
quay.io#calico#cni.tar
dockerload <
quay.io#calico#kube-controllers.tar
dockerload <
k8s.gcr.io#kube-proxy-amd64.tar
dockerload <
k8s.gcr.io#kube-scheduler-amd64.tar
dockerload <
k8s.gcr.io#kube-controller-manager-amd64.tar
dockerload <
k8s.gcr.io#kube-apiserver-amd64.tar
dockerload <
k8s.gcr.io#etcd-amd64.tar
dockerload <
k8s.gcr.io#k8s-dns-dnsmasq-nanny-amd64.tar
dockerload <
k8s.gcr.io#k8s-dns-sidecar-amd64.tar
dockerload <
k8s.gcr.io#k8s-dns-kube-dns-amd64.tar
dockerload <
k8s.gcr.io#pause-amd64.tar
dockerload <
quay.io#coreos#etcd.tar
dockerload <
quay.io#calico#node.tar
dockerload <
quay.io#calico#cni.tar
dockerload <
quay.io#calico#kube-policy-controller.tar
dockerload <
gcr.io#google_containers#etcd.tar
將鏡像與該腳本放置同一目錄,執行即可導入Docker鏡像。運行docker images
,如下圖所示,即表示鏡像導入成功:
image.png
三、複製虛擬機
前言中提到,當Node1的Kubernetes安裝完畢後,就需要進行虛擬機的複製了。
複製
複製前需要退出虛擬機,我們選擇“正常關機”。右鍵虛擬機點擊複製:
image.png
如上,新的節點命名爲CentOS-Node2
,注意一定要勾選"重新初始化網卡Mac地址"。點擊“複製”,稍等幾分鐘,即可完成複製:
image.png
依此法再複製一個節點命名爲CentOS-Node3
。
添加網卡
複製結束後,如果直接啓動三個虛擬機,你會發現每個機子的IP地址(網卡enp0s3)都是一樣的:
image.png
這是因爲複製虛擬機時連同網卡的地址也複製了,這樣的話,三個節點之間是無法訪問的。因此,我建議複製結束後,不要馬上啓動虛擬機,而先要爲每一個虛擬機添加一個網卡,用於節點間的互通訪問。
如下圖所示,連接方式選擇“Host-Only”模式:
image.png
網卡添加結束後,啓動三個虛擬機,查看各個IP。以主節點Node1爲例,運行ip addr
image.png
可以看到,網卡enp0s8爲新添加的網卡2,IP地址爲192.168.56.101
。三個節點IP分別爲:
Node1:192.168.56.101
Node2:192.168.56.102
Node3:192.168.56.103
在這三個節點中,可以使用這些IP互相ping一下,確保網絡連通正常。
另外,同上一節所述,建議啓用端口轉發功能,使用Xshell連接到Node1和Node2的終端。
設置虛擬機
網卡添加結束後,即可啓動三個虛擬機,我們需要進行一些簡單的設置,以主節點Node1爲例:
編輯/etc/hostname
,將hostname
修改爲k8s-node1
編輯/etc/hosts
,追加內容 IP k8s-node1
以上IP爲網卡2的IP地址,修改後重啓生效。另外兩個節點修改同理,主機名分別爲k8s-node2
、k8s-node3
。
四、創建集羣
kubeadm介紹
前面的工作都準備好後,我們就可以真正的創建集羣了。這裏使用的是官方提供的kubeadm工具,它可以快速、方便的創建一個K8S集羣。kubeadm的具體介紹大家可以參考官方文檔:https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/
。
截止目前,kubeadm尚處於beta狀態,官方暫時不推薦在生產環境使用,但是預計今年會推出GA版本。這裏,我建議大家儘量使用kubeadm,相對於純手動部署效率更高,也不容易出錯。
創建集羣
在Master主節點(k8s-node1)上執行:kubeadm init --pod-network-cidr=192.168.0.0/16--kubernetes-version=v1.10.0 --apiserver-advertise-address=192.168.56.101
含義:
1.選項--pod-network-cidr=192.168.0.0/16表示集羣將使用Calico網絡,這裏需要提前指定Calico的子網範圍
2.選項--kubernetes-version=v1.10.0指定K8S版本,這裏必須與之前導入到Docker鏡像版本v1.10.0一致,否則會訪問谷歌去重新下載K8S最新版的Docker鏡像
3.選項--apiserver-advertise-address表示綁定的網卡IP,這裏一定要綁定前面提到的enp0s8網卡,否則會默認使用enp0s3網卡
4.若執行kubeadm init出錯或強制終止,則再需要執行該命令時,需要先執行kubeadm reset重置
執行結果:
[root@k8s-node1 ~]
#kubeadm init --pod-network-cidr=192.168.0.0/16 --kubernetes-version=v1.10.0--apiserver-advertise-address=192.168.56.101
[init] Using Kubernetes version: v1
.10.0
[init] Using Authorization modes: [Node RBAC]
[preflight] Running pre-flight checks.
[WARNINGSystemVerification]: docker version
is greater than the most recentlyvalidated version. Docker version:
18.03.1-ce. Max validated version:
17.03
[WARNINGFileExisting-crictl]: crictl
not found
in system path
Suggestion: go getgithub.com/kubernetes-incubator/cri-tools/cmd/crictl
[certificates] Generated ca certificate
and key.
[certificates] Generated apiserver certificate
and key.
[certificates] apiserver serving cert
is signed
for DNS names [k8s-node1kubernetes kubernetes.default kubernetes.default.svckubernetes.default.svc.cluster.local]
and IPs [
10.96.0.1192.168.56.101]
[certificates] Generated apiserver-kubelet-client certificate
and key.
[certificates] Generated etcd/ca certificate
and key.
[certificates] Generated etcd/server certificate
and key.
[certificates] etcd/server serving cert
is signed
for DNS names [localhost]
and IPs [
127.0.0.1]
[certificates] Generated etcd/peer certificate
and key.
[certificates] etcd/peer serving cert
is signed
for DNS names [k8s-node1]
and IPs [
192.168.56.101]
[certificates] Generated etcd/healthcheck-client certificate
and key.
[certificates] Generated apiserver-etcd-client certificate
and key.
[certificates] Generated sa key
and public key.
[certificates] Generated front-proxy-ca certificate
and key.
[certificates] Generated front-proxy-client certificate
and key.
[certificates] Valid certificates
and keys now exist
in"/etc/kubernetes/pki"
[kubeconfig] Wrote KubeConfig file to disk:
"/etc/kubernetes/admin.conf"
[kubeconfig] Wrote KubeConfig file to disk:
"/etc/kubernetes/kubelet.conf"
[kubeconfig] Wrote KubeConfig file to disk:
"/etc/kubernetes/controller-manager.conf"
[kubeconfig] Wrote KubeConfig file to disk:
"/etc/kubernetes/scheduler.conf"
[controlplane] Wrote Static Pod manifest
for component kube-apiserver to
"/etc/kubernetes/manifests/kube-apiserver.yaml"
[controlplane] Wrote Static Pod manifest
for component kube-controller-manager to
"/etc/kubernetes/manifests/kube-controller-manager.yaml"
[controlplane] Wrote Static Pod manifest
for component kube-scheduler to
"/etc/kubernetes/manifests/kube-scheduler.yaml"
[etcd] Wrote Static Pod manifest
for a local etcd instance to
"/etc/kubernetes/manifests/etcd.yaml"
[init] Waiting
for the kubelet to boot up thecontrol plane
as Static Pods
from directory
"/etc/kubernetes/manifests".
[init] This might take a minute
or longer
if the control plane images haveto be pulled.
[apiclient] All control plane components are healthy after
24.006116 seconds
[uploadconfig] Storing the configuration used
in ConfigMap
"kubeadm-config"in the
"kube-system" Namespace
[markmaster] Will mark node k8s-node1
as master by adding a label
and a taint
[markmaster] Master k8s-node1 tainted
and labelled
with key/value:node-role.kubernetes.io/master=
""
[bootstraptoken] Using token: kt62dw.q99dfynu1kuf4wgy
[bootstraptoken] Configured RBAC rules to allow Node Bootstraptokens to post CSRs
in order
for nodes to get long term certificate credentials
[bootstraptoken] Configured RBAC rules to allow the csrapprovercontroller automatically approve CSRs
from a Node Bootstrap Token
[bootstraptoken] Configured RBAC rules to allow certificaterotation
for all node client certificates
in the cluster
[bootstraptoken] Creating the
"cluster-info" ConfigMap
in the
"kube-public" namespace
[addons] Applied essential addon: kube-dns
[addons] Applied essential addon: kube-proxy
Your Kubernetes master has initialized successfully!
To start using your cluster, you need to run the following
as a regular user:
mkdir -p $HOME/.kube
sudo cp -i/etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id-g) $HOME/.kube/config
You should now deploy a pod network to the cluster.
Run
"kubectl apply -f[podnetwork].yaml"with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
You can now join any number of machines by running the followingon each node
as root:
kubeadm join
192.168.56.101:
6443 --tokenkt62dw.q99dfynu1kuf4wgy --discovery-token-ca-cert-hash sha256:
5404bcccc1ade37e9d80831ce82590e6079c1a3ea52a941f3077b40ba19f2c68
可以看到,提示集羣成功初始化,並且我們需要執行以下命令:
mkdir -p $HOME/.kube
sudo cp -i/etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u)
:$(id -g) $HOME/.kube/config
另外,提示我們還需要創建網絡,並且讓其他節點執行kubeadm join...
加入集羣。
創建網絡
如果不創建網絡,查看pod狀態時,可以看到kube-dns組件是阻塞狀態,集羣時不可用的:
image.png
大家可以參考官方文檔,根據需求選擇適合的網絡,這裏,我們使用Calico(在前面初始化集羣的時候就已經確定了)。
根據官方文檔,在主節點上,需要執行如下命令:kubectl apply -fhttps://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/hosted/kubeadm/1.7/calico.yaml
但需要注意的是:
本文實驗時所使用的calico的docker鏡像版本爲v3.1.0,如下圖所示
image.png
但截至本文撰寫時,calico.yaml
文件中版本已升級爲v3.1.1。因此我們需要下載calico.yaml
,手動編輯文件修改爲v3.1.0並重新創建網絡。否則,執行kubectl apply
命令時,會重新拉取v3.1.1的鏡像導致超時失敗。同時,kube-dns模塊也會因爲網絡無法創建而Pending:
image.png
確保版本一致後,執行成功則提示:
image.png
image.png
五、集羣設置
將Master作爲工作節點
K8S集羣默認不會將Pod調度到Master上,這樣Master的資源就浪費了。在Master(即k8s-node1)上,可以運行以下命令使其作爲一個工作節點:kubectl taint nodes --all node-role.kubernetes.io/master-
利用該方法,我們可以不使用minikube而創建一個單節點的K8S集羣
執行成功後提示:
image.png
將其他節點加入集羣
在其他兩個節點k8s-node2和k8s-node3上,執行主節點生成的kubeadm join
命令即可加入集羣:kubeadm join 192.168.56.101:6443 --tokenkt62dw.q99dfynu1kuf4wgy --discovery-token-ca-cert-hashsha256:5404bcccc1ade37e9d80831ce82590e6079c1a3ea52a941f3077b40ba19f2c68
加入成功後,提示:
image.png
驗證集羣是否正常
當所有節點加入集羣后,稍等片刻,在主節點上運行kubectl getnodes
可以看到:
image.png
如上,若提示notReady則表示節點尚未準備好,可能正在進行其他初始化操作,等待全部變爲Ready即可。
大家可能會好奇,我們前面使用的是v1.10.0,爲何這裏版本是v1.10.2。實際上,這裏顯示是每個節點上kubelet程序的版本,即先前使用yum安裝時的默認版本,是向下兼容的。而v.1.10.0指的是K8S依賴的Docker鏡像版本,與kubeadm init
命令中一定要保持一致。
另外,建議查看所有pod狀態,運行kubectl getpods -n kube-system
:
image.png
如上,全部Running則表示集羣正常。至此,我們的K8S集羣就搭建成功了。走!去按摩一下頸椎,放鬆一下,真累啊!
4 從零開始搭建Kubernetes集羣(四、搭建K8S Dashboard)
一、前言
前面三篇文章介紹瞭如何從零開始搭建一個基本的Kubernetes集羣,本文將介紹一下如何搭建K8S的Dashboard。
簡單的說,K8SDashboard是官方的一個基於WEB的用戶界面,專門用來管理K8S集羣,並可展示集羣的狀態。K8S集羣安裝好後默認沒有包含Dashboard,我們需要額外創建它。
本人覺得Dashboard設計的還不錯,界面友好,功能也比較強大。如果你厭倦了命令行的操作,全程使用Dashboard也是可行的。
Dashboard的搭建過程中,會遇到一些坑。現在開始,咱們一步一步踩來,走你!
二、RABC簡介
還是那句話,官方文檔是最重要的參考資料:https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/
該文檔中,創建kubernetes-dashboard的命令爲:kubectl create -fhttps://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml
當然,直接這樣創建的dashboard會有很多問題,參見:
Please note,this works only if the apiserver is set up to allow authentication withusername and password. This is not currently the case with some setup tools(e.g., kubeadm
). Refer to the authentication admin documentation forinformation on how to configure authentication manually.
因爲我們使用kubeadm搭建的集羣會默認開啓RABC(角色訪問控制機制),所以我們必須要進行額外的設置。關於RABC的概念,網上資料很多,大家務必提前瞭解。這裏簡要介紹一下幾個重要概念:
RBAC
K8S 1.6引進,是讓用戶能夠訪問 k8S API 資源的授權方式【不授權就沒有資格訪問K8S的資源】
用戶
K8S有兩種用戶:User和Service Account。其中,User給人用,ServiceAccount給進程用,讓進程有相關權限。如Dashboard就是一個進程,我們就可以創建一個ServiceAccount給它
角色
Role是一系列權限的集合,例如一個Role可包含讀取和列出 Pod的權限【 ClusterRole 和 Role 類似,其權限範圍是整個集羣】
角色綁定
RoleBinding把角色映射到用戶,從而讓這些用戶擁有該角色的權限【ClusterRoleBinding 和RoleBinding 類似,可讓用戶擁有ClusterRole 的權限】
Secret
Secret是一個包含少量敏感信息如密碼,令牌,或祕鑰的對象。把這些信息保存在 Secret對象中,可以在這些信息被使用時加以控制,並可以降低信息泄露的風險
如下圖,灰色是“角色”,藍色是“用戶”,綠色是“角色綁定”,黃色是該角色擁有的權限。簡言之 ,角色綁定將角色和用戶進行掛鉤:
image.png
三、官方kubernetes-dashboard.yaml簡介
很有必要介紹一下官方的kubernetes-dashboard.yaml,我們首先將其下載下來:wgethttps://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml
該文件分爲以下幾部分:
DashboardService
DashboardDeployment
DashboardRole
RoleBinding
DashboardService Account
DashboardSecret
這裏,我們簡單的對各個部分的功能進行介紹:
Dashboard Role
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name:kubernetes-dashboard-minimal
namespace: kube-system
rules:
# Allow Dashboard to create 'kubernetes-dashboard-key-holder'secret.
- apiGroups: [
""]
resources: [
"secrets"]
verbs: [
"create"]
# Allow Dashboard to create 'kubernetes-dashboard-settings' configmap.
- apiGroups: [
""]
resources: [
"configmaps"]
verbs: [
"create"]
# Allow Dashboard to get, update and delete Dashboard exclusivesecrets.
- apiGroups: [
""]
resources: [
"secrets"]
resourceNames: [
"kubernetes-dashboard-key-holder",
"kubernetes-dashboard-certs"]
verbs: [
"get",
"update",
"delete"]
# Allow Dashboard to get and update'kubernetes-dashboard-settings' config map.
- apiGroups: [
""]
resources: [
"configmaps"]
resourceNames: [
"kubernetes-dashboard-settings"]
verbs: [
"get",
"update"]
# Allow Dashboard to get metrics from heapster.
- apiGroups: [
""]
resources: [
"services"]
resourceNames: [
"heapster"]
verbs: [
"proxy"]
- apiGroups: [
""]
resources: [
"services/proxy"]
resourceNames: [
"heapster",
"http:heapster:",
"https:heapster:"]
verbs: [
"get"]
如上定義了Dashboard的角色,其角色名稱爲kubernetes-dashboard-minimal
,rules
中清晰的列出了其擁有的多個權限。通過名稱我們可以猜到,這個權限級別是比較低的。
ServiceAccount
kind: ServiceAccount
metadata:
labels:
k8s-app:kubernetes-dashboard
name: kubernetes-dashboard
namespace: kube-system
如上定義了Dashboard的用戶,其類型爲ServiceAccount
,名稱爲kubernetes-dashboard
。
RoleBinding
kind:
RoleBinding
metadata:
name:
kubernetes-dashboard-minimal
namespace:
kube-system
roleRef:
apiGroup:
rbac.authorization.k8s.io
kind:
Role
name:
kubernetes-dashboard-minimal
subjects:
-kind:
ServiceAccount
name:
kubernetes-dashboard
namespace:
kube-system
如上定義了Dashboard的角色綁定,其名稱爲kubernetes-dashboard-minimal
,roleRef中爲被綁定的角色,也叫kubernetes-dashboard-minimal
,subjects
中爲綁定的用戶:kubernetes-dashboard
。
Dashboard Secret
kind: Secret
metadata:
labels:
k8s-app:kubernetes-dashboard
name:kubernetes-dashboard-certs
namespace: kube-system
type: Opaque
Dashboard Deployment
kind: Deployment
apiVersion: apps/v1beta2
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kube-system
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app:kubernetes-dashboard
template:
metadata:
labels:
k8s-app:kubernetes-dashboard
spec:
containers:
- name:kubernetes-dashboard
image:k8s.gcr.io/kubernetes-dashboard-amd64:v1.8.3
ports:
- containerPort:8443
protocol: TCP
args:
---auto-generate-certificates
# Uncomment the following line to manually specify Kubernetes APIserver Host
# If not specified, Dashboard will attempt to auto discover theAPI server and connect
# to it. Uncomment only if the default does not work.
#- --apiserver-host=http://my-address:port
volumeMounts:
- name:kubernetes-dashboard-certs
mountPath: /certs
# Create on-disk volume to store exec logs
- mountPath: /tmp
name: tmp-volume
livenessProbe:
httpGet:
scheme: HTTPS
path: /
port: 8443
initialDelaySeconds: 30
timeoutSeconds: 30
volumes:
- name:kubernetes-dashboard-certs
secret:
secretName:kubernetes-dashboard-certs
- name: tmp-volume
emptyDir: {}
serviceAccountName:kubernetes-dashboard
# Comment the following tolerations if Dashboard must not bedeployed on master
tolerations:
- key:node-role.kubernetes.io/master
effect: NoSchedule
如上可以看到,Dashboard的Deployment指定了其使用的ServiceAccount是kubernetes-dashboard
。並且還將Secret kubernetes-dashboard-certs
通過volumes掛在到pod內部的/certs路徑。爲何要掛載Secret ?原因是創建Secret 時會自動生成token。請注意參數--auto-generate-certificates
,其表示Dashboard會自動生成證書。
四、安裝Dashboard
1.導入鏡像
如果直接使用官方的kubernetes-dashboard.yaml創建Dashboard,你會踩到很多坑,首先是鏡像拉取會超時失敗。截止目前,Dashboard的最新版本是1.8.3,我已經將鏡像k8s.gcr.io#kubernetes-dashboard-amd64.tar
導出,提供給大家:
鏈接:https://pan.baidu.com/s/11AheivJxFzc4X6Q5_qCw8A
密碼:2zov
在所有節點上(因爲你不知道K8S會將Dashboard的pod調度到哪個節點),使用如下命令導入鏡像:docker load < k8s.gcr.io#kubernetes-dashboard-amd64.tar
導入成功後,執行docker images
可以看到Dashboard的版本是1.8.3:
image.png
2.創建Dashboard
導入鏡像後,使用之前下載的yaml文件即可創建Dashboard:kubectl create -f kubernetes-dashboard.yaml
3.訪問Dashboard
根據官方文檔,目前訪問Dashboard有四種方式:
NodePort
API Server
kubectl proxy
Ingress
以上四種方式,我測試了前三種,目前只有NodePort和kubectlproxy可用,API Server暫時沒有解決。
使用NodePort
爲kubernetes-dashboard.yaml添加Service後,就可以使用NodePort訪問Dashboard。在我們的物理機上,使用Chrome訪問https://192.168.56.101:32159/
,結果如下圖所示:
image.png
如上可以看到,這裏提示了證書錯誤NET::ERR_CERT_INVALID
,原因是由於物理機的瀏覽器證書不可用。但是,不要放棄,我們這裏不打算使用物理機訪問瀏覽器,而使用Dashboard所在節點上的瀏覽器來訪問(即CentOS自帶的瀏覽器),這樣的證書應該是可行的(官方默認就是這種方式)。
由於之前建立虛擬機環境時,我們關閉了CentOS的圖形界面,這裏我們爲了訪問Dashboard臨時開啓,執行:systemctlset-default graphical.target
。重啓後,即可進入圖形界面。我們用Firefox訪問:https://192.168.56.101:32159/
,成功後出現如下界面:
image.png
需要注意的是,若提示“連接不安全”的警告時,點擊“高級”,點擊“添加例外”後即可:
image.png
image.png
使用API Server
在我們的物理機上,使用Chrome訪問地址:https://192.168.56.101:6443/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/
,返回如下錯誤:
{
"kind":
"Status",
"apiVersion":
"v1",
"metadata": {
},
"status":
"Failure",
"message":
"services \"https:kubernetes-dashboard:\" isforbidden: User \"system:anonymous\" cannot get services/proxy in thenamespace \"kube-system\"",
"reason":
"Forbidden",
"details": {
"name":
"https:kubernetes-dashboard:",
"kind":
"services"
},
"code":
403
}
原因是由於kube-apiserver使用了TLS認證,而我們的真實物理機上的瀏覽器使用匿名證書(因爲沒有可用的證書)去訪問Dashboard,導致授權失敗而不無法訪問。官方提供的解決方法是將kubelet的證書轉化爲瀏覽器可用的證書,然後導入進瀏覽器。
Note: Thisway of accessing Dashboard is only possible if you choose to install your usercertificates in the browser. In example certificates used by kubeconfig file tocontact API Server can be used.
但是該方法目前似乎不適用於kubeadm方式安裝的集羣,參見:https://github.com/opsnull/follow-me-install-kubernetes-cluster/issues/5
那如果使用節點自帶的Firefox呢?我們在Firefox中訪問:https://192.168.56.101:6443/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/
,仍然提示上面的錯誤:
image.png
看來,無論物理機還是K8S節點上的瀏覽器,都需要導入這個證書,暫時無解。
使用kubectl proxy
這裏,我主要介紹一下最便捷的kubectl proxy
方式。在Master上執行kubecllproxy
,然後使用如下地址訪問Dashboard:http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy
但限制就是必須在Master上訪問,這顯然是個坑,我們的目標是在我們真實的物理機上去訪問Master的Dashboard。
所以,在主節點上,我們執行kubectlproxy --address=192.168.56.101 --disable-filter=true
開啓代理。
其中:
address
表示外界可以使用192.168.56.101
來訪問Dashboard,我們也可以使用0.0.0.0
disable-filter=true
表示禁用請求過濾功能,否則我們的請求會被拒絕,並提示 Forbidden(403) Unauthorized
。
我們也可以指定端口,具體請查看kubectlproxy --help
如下圖所示,proxy默認對Master的8001端口進行監聽:
image.png
這樣,我們就可以使用如下地址訪問登錄界面:http://192.168.56.101:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/#!/login
image.png
4.配置Dashboard
Dashboard的配置是難點,尤其是涉及到安全權限相關,相當複雜,坑也比較多。
進入Dashboard的登錄界面後,認證方式有Kubeconfig和令牌兩種方式(實際上還有賬號密碼的方式,默認不開啓不顯示)。看到Kubeconfig和令牌,估計頭都大了。是否有簡便的方法,讓我們能直接訪問Dashboard?當然有,選擇跳過,會出現如下頁面:
image.png
如上圖,很遺憾,我們看到了很多權限錯誤提示,主要是system:serviceaccount:kube-system:kubernetes-dashboard
的權限不足引起的。
我們回想本文第三小節對kubernetes-dashboard.yaml的介紹,現在就理解了爲什麼其角色的名稱爲kubernetes-dashboard-minimal
。一句話,這個Role的權限不夠!
因此,我們可以更改RoleBinding
修改爲ClusterRoleBinding
,並且修改roleRef
中的kind
和name
,使用cluster-admin
這個非常牛逼的CusterRole(超級用戶權限,其擁有訪問kube-apiserver的所有權限)。如下:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name:kubernetes-dashboard-minimal
namespace: kube-system
roleRef:
apiGroup:rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard
namespace: kube-system
修改後,重新創建kubernetes-dashboard.yaml
,Dashboard就可以擁有訪問整個K8S 集羣API的權限。我們重新訪問Dashboard,如下圖所示:
image.png
如上,一切正常,請在界面上盡情的亂點吧。另外,如果有興趣,你還可以安裝Dashboard的Heapster插件,這裏就不再介紹了。
5 從零開始搭建Kubernetes集羣(五、搭建K8S Ingress)
一、前言
上一文《從零開始搭建Kubernetes集羣(四、搭建K8S Dashboard)》介紹瞭如何搭建Dashboard。本篇將介紹如何搭建Ingress來訪問K8S集羣的Service。
二、Ingress簡介
Ingress是個什麼鬼,網上資料很多(推薦官方),大家自行研究。簡單來講,就是一個負載均衡的玩意,其主要用來解決使用NodePort暴露Service的端口時Node IP會漂移的問題。同時,若大量使用NodePort暴露主機端口,管理會非常混亂。
好的解決方案就是讓外界通過域名去訪問Service,而無需關心其NodeIP及Port。那爲什麼不直接使用Nginx?這是因爲在K8S集羣中,如果每加入一個服務,我們都在Nginx中添加一個配置,其實是一個重複性的體力活,只要是重複性的體力活,我們都應該通過技術將它幹掉。
Ingress就可以解決上面的問題,其包含兩個組件Ingress Controller和Ingress:
Ingress
將Nginx的配置抽象成一個Ingress對象,每添加一個新的服務只需寫一個新的Ingress的yaml文件即可
IngressController
將新加入的Ingress轉化成Nginx的配置文件並使之生效
好了,廢話不多,走你~
三、準備操作
官方文檔
人生苦短,不造輪子,本文將以官方的標準腳本爲基礎進行搭建,參考請戳官方文檔。官方文檔中要求依次執行如下命令:
curl
https:/
/raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/namespace.yaml \
| kubectl apply -f -
curlhttps://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/default-backend.yaml\
| kubectl apply -f -
curl
https:/
/raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/configmap.yaml \
| kubectl apply -f -
curlhttps://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/tcp-services-configmap.yaml\
| kubectl apply -f -
curl
https:/
/raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/udp-services-configmap.yaml \
| kubectl apply -f -
以上yaml文件創建Ingress用到的Namespace、ConfigMap,以及默認的後端default-backend
。最關鍵的一點是,由於之前我們基於Kubeadm創建了K8S集羣,則還必須執行:
curl
https:/
/raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/rbac.yaml \
| kubectl apply -f -
curl https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/with-rbac.yaml\
| kubectl apply -f -
這是由於Kubeadm創建的集羣默認開啓了RABC,因此Ingress也必須創建相應的RABC權限控制。
導入鏡像
但是,直接按照上述方式執行,我們的Ingress很可能會無法使用。所以,我們需要將上述Yaml文件全部wget
下來,經過一些修改後才能執行kubectlapply -f
創建。另外需要注意的是,這些yaml文件中提到的一些鏡像,國內目前無法下載,如:
gcr.io/google_containers/defaultbackend:1.4
quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.14.0
本人已經提前下載好,大家請戳:
地址:
https:
//pan.baidu.com/s/1N-bK9hI7JTZZB6AzmaT8PA
密碼:
1a8a
拿到鏡像後,在每個節點上執行如下命令導入鏡像:
docker load < quay.io
#kubernetes-ingress-controller#nginx-ingress-controller_0.14.0.tar
docker tag 452a96d81c30quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.14.0
docker load < gcr.io
#google_containers#defaultbackend.tar
docker tag 452a96d81c30 gcr.io/google_containers/defaultbackend
如上所示,導入鏡像後,別忘記給打tag,否則鏡像名稱爲<none>:
image.png
四、主要文件介紹
這裏,我們先對一些重要的文件進行簡單介紹。
default-backend.yaml
default-backend
的作用是,如果外界訪問的域名不存在的話,則默認轉發到default-http-backend
這個Service,其會直接返回404:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name:
default-http-backend
labels:
app:
default-http-backend
namespace: ingress-nginx
spec:
replicas:
1
selector:
matchLabels:
app:
default-http-backend
template:
metadata:
labels:
app:
default-http-backend
spec:
terminationGracePeriodSeconds:
60
containers:
- name:
default-http-backend
# Any image ispermissible as
long as:
#
1. It serves a
404 page at /
#
2. It serves
200 on a /healthz endpoint
image:gcr.io/google_containers/defaultbackend:
1.4
livenessProbe:
httpGet:
path: /healthz
port:
8080
scheme: HTTP
initialDelaySeconds:
30
timeoutSeconds:
5
ports:
- containerPort:
8080
resources:
limits:
cpu:
10m
memory:
20Mi
requests:
cpu:
10m
memory:
20Mi
---
apiVersion: v1
kind: Service
metadata:
name:
default-http-backend
namespace: ingress-nginx
labels:
app:
default-http-backend
spec:
ports:
- port:
80
targetPort:
8080
selector:
app:
default-http-backend
rbac.yaml
rbac.yaml
負責Ingress的RBAC授權的控制,其創建了Ingress用到的ServiceAccount、ClusterRole、Role、RoleBinding、ClusterRoleBinding。在上文《從零開始搭建Kubernetes集羣(四、搭建K8S Dashboard)》中,我們已對這些概念進行了簡單介紹。
apiVersion: v1
kind: ServiceAccount
metadata:
name:nginx-ingress-serviceaccount
namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name:nginx-ingress-clusterrole
rules:
- apiGroups:
-
""
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
verbs:
-
list
- watch
- apiGroups:
-
""
resources:
- nodes
verbs:
- get
- apiGroups:
-
""
resources:
- services
verbs:
- get
-
list
- watch
- apiGroups:
-
"extensions"
resources:
- ingresses
verbs:
- get
-
list
- watch
- apiGroups:
-
""
resources:
- events
verbs:
- create
- patch
- apiGroups:
-
"extensions"
resources:
- ingresses/status
verbs:
- update
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
name: nginx-ingress-role
namespace: ingress-nginx
rules:
- apiGroups:
-
""
resources:
- configmaps
- pods
- secrets
- namespaces
verbs:
- get
- apiGroups:
-
""
resources:
- configmaps
resourceNames:
# Defaults to
"<election-id>-<ingress-class>"
# Here:
"<ingress-controller-leader>-<nginx>"
# This has to beadapted
if you change either parameter
# when launching the nginx-ingress-controller.
-
"ingress-controller-leader-nginx"
verbs:
- get
- update
- apiGroups:
-
""
resources:
- configmaps
verbs:
- create
- apiGroups:
-
""
resources:
- endpoints
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name:nginx-ingress-role-nisa-binding
namespace: ingress-nginx
roleRef:
apiGroup:rbac.authorization.k8s.io
kind: Role
name: nginx-ingress-role
subjects:
- kind: ServiceAccount
name:nginx-ingress-serviceaccount
namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name:nginx-ingress-clusterrole-nisa-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name:nginx-ingress-clusterrole
subjects:
- kind: ServiceAccount
name:nginx-ingress-serviceaccount
namespace: ingress-nginx
with-rbac.yaml
with-rbac.yaml
是Ingress的核心,用於創建ingress-controller。前面提到過,ingress-controller的作用是將新加入的Ingress進行轉化爲Nginx的配置。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name:nginx-ingress-controller
namespace: ingress-nginx
spec:
replicas:
1
selector:
matchLabels:
app: ingress-nginx
template:
metadata:
labels:
app: ingress-nginx
annotations:
prometheus.io/port:
'10254'
prometheus.io/scrape:
'true'
spec:
serviceAccountName:nginx-ingress-serviceaccount
containers:
- name:nginx-ingress-controller
image:quay.io/kubernetes-ingress-controller/nginx-ingress-controller:
0.14.0
args:
-/nginx-ingress-controller
- --
default-backend-service=$(POD_NAMESPACE)/
default-http-backend
- --configmap=$(POD_NAMESPACE)/nginx-configuration
---tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
---udp-services-configmap=$(POD_NAMESPACE)/udp-services
---annotations-prefix=nginx.ingress.kubernetes.io
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath:metadata.name
- name:POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath:metadata.namespace
ports:
- name: http
containerPort:
80
- name: https
containerPort:
443
livenessProbe:
failureThreshold:
3
httpGet:
path: /healthz
port:
10254
scheme: HTTP
initialDelaySeconds:
10
periodSeconds:
10
successThreshold:
1
timeoutSeconds:
1
readinessProbe:
failureThreshold:
3
httpGet:
path: /healthz
port:
10254
scheme: HTTP
periodSeconds:
10
successThreshold:
1
timeoutSeconds:
1
securityContext:
runAsNonRoot:
false
如上,可以看到nginx-ingress-controller啓動時傳入了參數,分別爲前面創建的default-backend-service以及configmap。
五、創建Ingress
1.創建Ingress-controller
需要注意的是,官方提供的with-rbac.yaml
文件不能直接使用,我們必須修改兩處:
加入hostNetwork配置
如下,在serviceAccountName
上方添加hostNetwork:true
:
spec:
hostNetwork:
true
serviceAccountName:nginx-ingress-serviceaccount
containers:
- name:nginx-ingress-controller
image:quay.io/kubernetes-ingress-controller/nginx-ingress-controller:
0.14.0
args:
-/nginx-ingress-controller
- --
default-backend-service=$(POD_NAMESPACE)/
default-http-backend
---configmap=$(POD_NAMESPACE)/nginx-configuration
---tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
---udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --annotations-prefix=nginx.ingress.kubernetes.io
注:現在下載with-rbac.yaml,配置的ingress版本爲0.19.0,需要修改爲我們安裝的版本0.14.0;
配置hostNetwork: true
是一種直接定義Pod網絡的方式。定義後,Ingress-controller的IP就與宿主機k8s-node1一樣(192.168.56.101),並且端口80也是宿主機上的端口。這樣,我們通過該192.168.56.101:80
,就可以直接訪問到Ingress-controller(實際上就是nginx),然後Ingress-controller則會轉發我們的請求到相應後端。
加入環境變量
在其env部分加入如下環境變量:
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath:metadata.name
- name:POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.
namespace
- name:KUBERNETES_MASTER
value: http:
//192.168.56.101:8080
否則,創建後會提示如下錯誤:
[root@k8s-node1 ingress]
#kubectl describe pod nginx-ingress-controller-9fbd7596d-rt9sf -n ingress-nginx
省略前面
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled
30s
default-scheduler Successfullyassigned nginx-ingress-controller
-9fbd7596d-rt9sf to k8s-node1
Normal SuccessfulMountVolume
30s kubelet, k8s-node1 MountVolume.SetUp succeeded
for volume
"nginx-ingress-serviceaccount-token-lq2dt"
Warning BackOff
21s kubelet, k8s-node1 Back-off restarting failed container
Normal Pulled
11s (x3 over
29s) kubelet, k8s-node1 Container image
"quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.14.0" already present on machine
Normal Created
11s (x3 over
29s) kubelet, k8s-node1 Created container
Warning Failed
10s (x3 over
28s) kubelet, k8s-node1 Error: failed to start container
"nginx-ingress-controller": Error response from daemon:OCI runtime create failed: container_linux.go:
348: starting container process caused
"exec: \"/nginx-ingress-controller\": stat/nginx-ingress-controller: no such file or directory": unknown
修改with-rbac.yaml
後,使用kubectl create -f
命令分別執行如下yaml文件,即可創建Ingress-controller:
image.png
創建成功後如下所示:
[root@k8s-node1 ingress]
#kubectl get pod -n ingress-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE
default-http-backend
-5c6d95c48-pdjn9
1/
1 Running
0
23s
192.168.36.81 k8s-node1
nginx-ingress-controller
-547cd7d9cb-jmvpn
1/
1 Running
0
8s
192.168.36.82 k8s-node1
2.創建自定義Ingress
有了ingress-controller,我們就可以創建自定義的Ingress了。這裏已提前搭建好了Kibana服務,我們針對Kibana創建一個Ingress:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: kibana-ingress
namespace:
default
spec:
rules:
- host: myk8s.com
http:
paths:
- path: /
backend:
serviceName:kibana
servicePort:
5601
其中:
rules中的host必須爲域名,不能爲IP,表示Ingress-controller的Pod所在主機域名,也就是Ingress-controller的IP對應的域名。
paths中的path則表示映射的路徑。如映射/
表示若訪問myk8s.com
,則會將請求轉發至Kibana的service,端口爲5601。
創建成功後,查看:
[root@k8s-node1 ingress]
#kubectl get ingress -o wide
NAME HOSTS ADDRESS PORTS AGE
kibana-ingress myk8s.com
80
6s
我們再執行kubectl execnginx-ingress-controller-5b79cbb5c6-2zr7f -it cat /etc/nginx/nginx.conf -ningress-nginx
,可以看到生成nginx配置,篇幅較長,各位自行篩選:
## start server myk8s.com
server {
server_name myk8s.com ;
listen 80;
listen [::]:80;
set$proxy_upstream_name"-";
location /kibana {
log_by_lua_block{
}
port_in_redirectoff;
set$proxy_upstream_name"";
set$namespace
"kube-system";
set$ingress_name
"dashboard-ingress";
set$service_name
"kibana";
client_max_body_size
"1m";
proxy_set_headerHost
$best_http_host;
# Pass the extracted client certificate to the backend
# Allow websocket connections
proxy_set_header Upgrade
$http_upgrade;
proxy_set_header Connection
$connection_upgrade;
proxy_set_headerX-Real-IP
$the_real_ip;
proxy_set_headerX-Forwarded-For
$the_real_ip;
proxy_set_headerX-Forwarded-Host
$best_http_host;
proxy_set_headerX-Forwarded-Port
$pass_port;
proxy_set_headerX-Forwarded-Proto
$pass_access_scheme;
proxy_set_headerX-Original-URI
$request_uri;
proxy_set_headerX-Scheme
$pass_access_scheme;
# Pass the original X-Forwarded-For
proxy_set_headerX-Original-Forwarded-For
$http_x_forwarded_for;
# mitigate HTTPoxy Vulnerability
# https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/
proxy_set_headerProxy
"";
# Custom headers to proxied server
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffering
"off";
proxy_buffer_size
"4k";
proxy_buffers 4
"4k";
proxy_request_buffering
"on";
proxy_http_version 1.1;
proxy_cookie_domain off;
proxy_cookie_path off;
# In case of errors try the next upstream server before returningan error
proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;
proxy_next_upstream_tries 0;
# No endpoints available for the request
return 503;
}
location / {
log_by_lua_block{
}
port_in_redirectoff;
set$proxy_upstream_name"";
set$namespace
"default";
set$ingress_name
"kibana-ingress";
set$service_name
"kibana";
client_max_body_size
"1m";
proxy_set_headerHost
$best_http_host;
# Pass the extracted client certificate to the backend
# Allow websocket connections
proxy_set_header Upgrade
$http_upgrade;
proxy_set_header Connection
$connection_upgrade;
proxy_set_headerX-Real-IP
$the_real_ip;
proxy_set_headerX-Forwarded-For
$the_real_ip;
proxy_set_headerX-Forwarded-Host
$best_http_host;
proxy_set_headerX-Forwarded-Port
$pass_port;
proxy_set_headerX-Forwarded-Proto
$pass_access_scheme;
proxy_set_headerX-Original-URI
$request_uri;
proxy_set_headerX-Scheme
$pass_access_scheme;
# Pass the original X-Forwarded-For
proxy_set_headerX-Original-Forwarded-For
$http_x_forwarded_for;
# mitigate HTTPoxy Vulnerability
#https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/
proxy_set_headerProxy
"";
# Custom headers to proxied server
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffering
"off";
proxy_buffer_size
"4k";
proxy_buffers 4
"4k";
proxy_request_buffering
"on";
proxy_http_version 1.1;
proxy_cookie_domain off;
proxy_cookie_path off;
# In case of errors try the next upstream server before returningan error
proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;
proxy_next_upstream_tries 0;
# No endpoints available for the request
return 503;
}
}
## end server myk8s.com
3.設置host
首先,我們需要在Ingress-controller的Pod所在主機上(這裏爲k8s-node1),將上面提到的域名myk8s.com
追加入/etc/hosts
文件:
192
.168.56.101myk8s.com
除此之外,如果想在自己的Windows物理機上使用瀏覽器訪問kibana,也需要在C:\Windows\System32\drivers\etc\hosts
文件內加入上述內容。設置後,分別在k8s-node1和物理機上測試無誤即可:
image.png
image.png
六、測試
在Windows物理機上,使用Chrome訪問myk8s.com
,也就是相當於訪問了192.168.56.101:80
:
image.png
隨意訪問一個錯誤的地址myk8s.com/abc
,返回預期的404:
image.png
6 從零開始搭建Kubernetes集羣(六、在K8S上部署Redis 集羣)
一、前言
上一文《從零開始搭建Kubernetes集羣(五、搭建K8S Ingress)》主要介紹瞭如何在K8S上搭建Ingress,以及如何通過Ingress訪問後端服務。本篇將介紹如何在K8S上部署Redis集羣。注意,這裏所說的Redis 集羣,指Redis Cluster而非Sentinel模式集羣。
下圖爲Redis集羣的架構圖,每個Master都可以擁有多個Slave。當Master下線後,Redis集羣會從多個Slave中選舉出一個新的Master作爲替代,而舊Master重新上線後變成新Master的Slave。
image.png
二、準備操作
本次部署主要基於該項目:
https:
//github.com/zuxqoj/kubernetes-redis-cluster
其包含了兩種部署Redis集羣的方式:
StatefulSet
Service&Deployment
兩種方式各有優劣,對於像Redis、Mongodb、Zookeeper等有狀態的服務,使用StatefulSet是首選方式。本文將主要介紹如何使用StatefulSet進行Redis集羣的部署。
三、StatefulSet簡介
StatefulSet的概念非常重要,簡單來說,其就是爲了解決Pod重啓、遷移後,Pod的IP、主機名等網絡標識會改變而帶來的問題。IP變化對於有狀態的服務是難以接受的,如在Zookeeper集羣的配置文件中,每個ZK節點都會記錄其他節點的地址信息:
tickTime=
2000
dataDir=
/home/myname/zookeeper
clientPort=
2181
initLimit=
5
syncLimit=
2
server
.1=
192.168.229.160:
2888:
3888
server
.2=
192.168.229.161:
2888:
3888
server
.3=
192.168.229.162:
2888:
3888
但若某個ZK節點的Pod重啓後改變了IP,那麼就會導致該節點脫離集羣,而如果該配置文件中不使用IP而使用IP對應的域名,則可避免該問題:
server.1=zk-node1:2888:3888
server.2=zk-node2:2888:3888
server.3=zk-node3:2888:3888
也即是說,對於有狀態服務,我們最好使用固定的網絡標識(如域名信息)來標記節點,當然這也需要應用程序的支持(如Zookeeper就支持在配置文件中寫入主機域名)。
StatefulSet基於Headless Service(即沒有Cluster IP的Service)爲Pod實現了穩定的網絡標誌(包括Pod的hostname和DNS Records),在Pod重新調度後也保持不變。同時,結合PV/PVC,StatefulSet可以實現穩定的持久化存儲,就算Pod重新調度後,還是能訪問到原先的持久化數據。
下圖爲使用StatefulSet部署Redis的架構,無論是Master還是Slave,都作爲StatefulSet的一個副本,並且數據通過PV進行持久化,對外暴露爲一個Service,接受客戶端請求。
image.png
四、部署過程
本文參考項目的README中,簡要介紹了基於StatefulSet的Redis創建步驟:
創建NFS存儲
創建PV
創建PVC
創建Configmap
創建headless服務
創建Redis StatefulSet
初始化Redis集羣
這裏,我們將參考如上步驟,實踐操作並詳細介紹Redis集羣的部署過程。文中會涉及到很多K8S的概念,希望大家能提前瞭解學習。
1.創建NFS存儲
創建NFS存儲主要是爲了給Redis提供穩定的後端存儲,當Redis的Pod重啓或遷移後,依然能獲得原先的數據。這裏,我們先要創建NFS,然後通過使用PV爲Redis掛載一個遠程的NFS路徑。
安裝NFS
由於硬件資源有限,我們可以在k8s-node2上搭建。執行如下命令安裝NFS和rpcbind:
yum -y install nfs-utils rpcbind
其中,NFS依靠遠程過程調用(RPC)在客戶端和服務器端路由請求,因此需要安裝rpcbind服務。
然後,新增/etc/exports
文件,用於設置需要共享的路徑:
/usr/
local/k8s/redis/pv1 *(rw,all_squash)
/usr/
local/k8s/redis/pv2 *(rw,all_squash)
/usr/
local/k8s/redis/pv3 *(rw,all_squash)
/usr/
local/k8s/redis/pv4 *(rw,all_squash)
/usr/
local/k8s/redis/pv5 *(rw,all_squash)
/usr/
local/k8s/redis/pv6 *(rw,all_squash)
如上,rw表示讀寫權限;all_squash 表示客戶機上的任何用戶訪問該共享目錄時都映射成服務器上的匿名用戶(默認爲nfsnobody);*
表示任意主機都可以訪問該共享目錄,也可以填寫指定主機地址,同時支持正則,如:
/root/share/ 192.168.1.20 (rw,all_squash)
/home/ljm/ *.gdfs.edu.cn (rw,all_squash)
由於我們打算創建一個6節點的Redis集羣,所以共享了6個目錄。當然,我們需要在k8s-node2上創建這些路徑,並且爲每個路徑修改權限:
chmod 777 /usr/
local/k8s/redis/pv*
這一步必不可少,否則掛載時會出現mount.nfs:access denied by server while mounting
的權限錯誤。
接着,啓動NFS和rpcbind服務:
systemctl start rpcbind
systemctl start nfs
我們在k8s-node1上測試一下,執行:
mount -t nfs
192.168.
56.102:/usr/local/k8s/redis/pv1 /mnt
表示將k8s-node2上的共享目錄/usr/local/k8s/redis/pv1
映射爲k8s-node1的/mnt
目錄,我們在/mnt中創建文件:
touch haha
既可以在k8s-node2上看到該文件:
[root@k8s-node2 redis]
#ll pv1
總用量
0
-rw-r--r--.
1 nfsnobody nfsnobody
05月
221:35 haha
可以看到用戶和組爲nfsnobody
。
創建PV
每一個RedisPod都需要一個獨立的PV來存儲自己的數據,因此可以創建一個pv.yaml
文件,包含6個PV:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv1
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: 192.168.56.102
path:
"/usr/local/k8s/redis/pv1"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-vp2
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: 192.168.56.102
path:
"/usr/local/k8s/redis/pv2"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv3
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: 192.168.56.102
path:
"/usr/local/k8s/redis/pv3"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv4
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: 192.168.56.102
path:
"/usr/local/k8s/redis/pv4"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv5
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: 192.168.56.102
path:
"/usr/local/k8s/redis/pv5"
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv6
spec:
capacity:
storage: 200M
accessModes:
- ReadWriteMany
nfs:
server: 192.168.56.102
path:
"/usr/local/k8s/redis/pv6"
如上,可以看到所有PV除了名稱和掛載的路徑外都基本一致。執行創建即可:
[root@k8s-node1 redis]
#kubectl create -f pv.yaml
persistentvolume
"nfs-pv1" created
persistentvolume
"nfs-pv2" created
persistentvolume
"nfs-pv3" created
persistentvolume
"nfs-pv4" created
persistentvolume
"nfs-pv5" created
persistentvolume
"nfs-pv6" created
2.創建Configmap
這裏,我們可以直接將Redis的配置文件轉化爲Configmap,這是一種更方便的配置讀取方式。配置文件redis.conf
如下:
appendonly yes
cluster-enabled yes
cluster-config-file /
var/lib/redis/nodes.conf
cluster-node-timeout
5000
dir /
var/lib/redis
port
6379
創建名爲redis-conf
的Configmap:
kubectl create configmap redis-conf --
from-file=redis.conf
查看:
[root@k8s-node1 redis]# kubectl describe cm redis-conf
Name: redis-conf
Namespace: default
Labels:
<none>
Annotations:
<none>
Data
====
redis.conf:
----
appendonly yes
cluster-enabled yes
cluster-config-file /var/lib/redis/nodes.conf
cluster-node-timeout 5000
dir /var/lib/redis
port 6379
Events:
<none>
如上,redis.conf
中的所有配置項都保存到redis-conf
這個Configmap中。
3.創建Headlessservice
Headlessservice是StatefulSet實現穩定網絡標識的基礎,我們需要提前創建。準備文件headless-service.yml
如下:
apiVersion: v1
kind: Service
metadata:
name: redis-service
labels:
app: redis
spec:
ports:
- name: redis-port
port:
6379
clusterIP:
None
selector:
app: redis
appCluster:redis-cluster
創建:
kubectlcreate-fheadless-service.yml
查看:
[root@k8s-node1 redis]
#kubectl get svc redis-service
NAME TYPE
CLUSTER-IP EXTERNAL-IP PORT(S) AGE
redis-service ClusterIP None <none>
6379/TCP
53s
可以看到,服務名稱爲redis-service
,其CLUSTER-IP
爲None
,表示這是一個“無頭”服務。
4.創建Redis 集羣節點
創建好Headlessservice後,就可以利用StatefulSet創建Redis 集羣節點,這也是本文的核心內容。我們先創建redis.yml文件:
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: redis-app
spec:
serviceName:
"redis-service"
replicas:
6
template:
metadata:
labels:
app: redis
appCluster:redis-cluster
spec:
terminationGracePeriodSeconds:
20
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight:
100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- redis
topologyKey:kubernetes.io/hostname
containers:
- name: redis
image:
"redis"
command:
-
"redis-server"
args:
-
"/etc/redis/redis.conf"
-
"--protected-mode"
-
"no"
resources:
requests:
cpu:
"100m"
memory:
"100Mi"
ports:
- name: redis
containerPort:
6379
protocol:
"TCP"
- name: cluster
containerPort:
16379
protocol:
"TCP"
volumeMounts:
- name:
"redis-conf"
mountPath:
"/etc/redis"
- name:
"redis-data"
mountPath:
"/var/lib/redis"
volumes:
- name:
"redis-conf"
configMap:
name:
"redis-conf"
items:
- key:
"redis.conf"
path:
"redis.conf"
volumeClaimTemplates:
- metadata:
name: redis-data
spec:
accessModes: [
"ReadWriteMany" ]
resources:
requests:
storage:
200M
如上,總共創建了6個Redis節點(Pod),其中3個將用於master,另外3個分別作爲master的slave;Redis的配置通過volume將之前生成的redis-conf
這個Configmap,掛載到了容器的/etc/redis/redis.conf
;Redis的數據存儲路徑使用volumeClaimTemplates聲明(也就是PVC),其會綁定到我們先前創建的PV上。
這裏有一個關鍵概念——Affinity,請參考官方文檔詳細瞭解。其中,podAntiAffinity表示反親和性,其決定了某個pod不可以和哪些Pod部署在同一拓撲域,可以用於將一個服務的POD分散在不同的主機或者拓撲域中,提高服務本身的穩定性。
而PreferredDuringSchedulingIgnoredDuringExecution則表示,在調度期間儘量滿足親和性或者反親和性規則,如果不能滿足規則,POD也有可能被調度到對應的主機上。在之後的運行過程中,系統不會再檢查這些規則是否滿足。
在這裏,matchExpressions規定了Redis Pod要儘量不要調度到包含app爲redis的Node上,也即是說已經存在Redis的Node上儘量不要再分配Redis Pod了。但是,由於我們只有三個Node,而副本有6個,因此根據PreferredDuringSchedulingIgnoredDuringExecution,這些豌豆不得不得擠一擠,擠擠更健康~
另外,根據StatefulSet的規則,我們生成的Redis的6個Pod的hostname會被依次命名爲$(statefulset名稱)-$(序號),如下圖所示:
[root@k8s-node1 redis]
#kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
dns-test
0/
1 Completed
0
52m
192.168.
169.208 k8s-node2
redis-app-
0
1/
1 Running
0
1h
192.168.
169.207 k8s-node2
redis-app-
1
1/
1 Running
0
1h
192.168.
169.197 k8s-node2
redis-app-
2
1/
1 Running
0
1h
192.168.
169.198 k8s-node2
redis-app-
3
1/
1 Running
0
1h
192.168.
169.205 k8s-node2
redis-app-
4
1/
1 Running
0
1h
192.168.
169.200 k8s-node2
redis-app-
5
1/
1 Running
0
1h
192.168.
169.201 k8s-node2
如上,可以看到這些Pods在部署時是以{0..N-1}的順序依次創建的。注意,直到redis-app-0狀態啓動後達到Running狀態之後,redis-app-1 纔開始啓動。
同時,每個Pod都會得到集羣內的一個DNS域名,格式爲$(podname).$(servicename).$(namespace).svc.cluster.local
,也即是:
redis-app-0.redis-service.default.svc.cluster.local
redis-app-1.redis-service.default.svc.cluster.local
...
以此類推
...
在K8S集羣內部,這些Pod就可以利用該域名互相通信。我們可以使用busybox鏡像的nslookup檢驗這些域名:
[root@k8s-node1 ~]
#kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh
If you don't see a command prompt,
try pressing enter.
/
# nslookupredis-app-0.redis-service
Server:
10.96.0.10
Address
1:
10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: redis-app
-0.redis-service
Address
1:
192.168.169.207 redis-app
-0.redis-service.
default.svc.cluster.local
可以看到,redis-app-0的IP爲192.168.169.207。當然,若Redis Pod遷移或是重啓(我們可以手動刪除掉一個Redis Pod來測試),則IP是會改變的,但Pod的域名、SRV records、A record都不會改變。
另外可以發現,我們之前創建的pv都被成功綁定了:
[root@k8s-node1 ~]
#kubectl get pv
NAME
CAPACITY ACCESS MODES RECLAIM POLICY STATUS
CLAIM STORAGECLASS REASON AGE
nfs-pv1
200M RWX Retain Bound
default/redis-data-redis-app
-2
1h
nfs-pv2
200M RWX Retain Bound
default/redis-data-redis-app
-3
1h
nfs-pv3
200M RWX Retain Bound
default/redis-data-redis-app
-4
1h
nfs-pv4
200M RWX Retain Bound
default/redis-data-redis-app
-5
1h
nfs-pv5
200M RWX Retain Bound
default/redis-data-redis-app
-0
1h
nfs-pv6
200M RWX Retain Bound
default/redis-data-redis-app
-1
1h
5.初始化Redis集羣
創建好6個Redis Pod後,我們還需要利用常用的Redis-tribe工具進行集羣的初始化。
創建Ubuntu容器
由於Redis集羣必須在所有節點啓動後才能進行初始化,而如果將初始化邏輯寫入Statefulset中,則是一件非常複雜而且低效的行爲。這裏,本人不得不稱讚一下原項目作者的思路,值得學習。也就是說,我們可以在K8S上創建一個額外的容器,專門用於進行K8S集羣內部某些服務的管理控制。
這裏,我們專門啓動一個Ubuntu的容器,可以在該容器中安裝Redis-tribe,進而初始化Redis集羣,執行:
kubectl run -i --tty ubuntu --image=ubuntu --restart=Never/bin/bash
成功後,我們可以進入ubuntu容器中,原項目要求執行如下命令安裝基本的軟件環境:
apt-getupdate
apt-getinstall-yvimwgetpython2.7python-pipredis-toolsdnsutils
但是,需要注意的是,在我們天朝,執行上述命令前需要提前做一件必要的工作——換源,否則你懂得。我們使用阿里雲的Ubuntu源,執行:
root@ubuntu:/
#cat > /etc/apt/sources.list << EOF
> deb http:
//mirrors.aliyun.com/ubuntu/bionic main restricted universe multiverse
> deb-src http:
//mirrors.aliyun.com/ubuntu/bionic main restricted universe multiverse
>
> deb http:
//mirrors.aliyun.com/ubuntu/bionic-security main restricted universe multiverse
> deb-src http:
//mirrors.aliyun.com/ubuntu/bionic-security main restricted universe multiverse
>
> deb http:
//mirrors.aliyun.com/ubuntu/bionic-updates main restricted universe multiverse
> deb-src http:
//mirrors.aliyun.com/ubuntu/bionic-updates main restricted universe multiverse
>
> deb http:
//mirrors.aliyun.com/ubuntu/bionic-proposed main restricted universe multiverse
> deb-src http:
//mirrors.aliyun.com/ubuntu/bionic-proposed main restricted universe multiverse
>
> deb http:
//mirrors.aliyun.com/ubuntu/bionic-backports main restricted universe multiverse
> deb-src http:
//mirrors.aliyun.com/ubuntu/bionic-backports main restricted universe multiverse
> EOF
源修改完畢後,就可以執行上面的兩個命令。
初始化集羣
首先,我們需要安裝redis-trib
:
pip install redis-trib
然後,創建只有Master節點的集羣:
redis-trib.pycreate \
`
dig +
shortredis-app-0.redis-service.default.svc.cluster.local`
:6379 \
`
dig +
shortredis-app-1.redis-service.default.svc.cluster.local`
:6379 \
`
dig +
shortredis-app-2.redis-service.default.svc.cluster.local`
:6379
如上,命令dig +shortredis-app-0.redis-service.default.svc.cluster.local
用於將Pod的域名轉化爲IP,這是因爲redis-trib
不支持域名來創建集羣。
其次,爲每個Master添加Slave:
redis-trib.pyreplicate \
--master-addr `
dig +
shortredis-app-0.redis-service.default.svc.cluster.local`
:6379 \
--slave-addr `
dig +
shortredis-app-3.redis-service.default.svc.cluster.local`
:6379
redis-trib.pyreplicate \
--master-addr `
dig +
shortredis-app-1.redis-service.default.svc.cluster.local`
:6379 \
--slave-addr `
dig +
shortredis-app-4.redis-service.default.svc.cluster.local`
:6379
redis-trib.pyreplicate \
--master-addr `
dig +
shortredis-app-2.redis-service.default.svc.cluster.local`
:6379 \
--slave-addr `
dig +
shortredis-app-5.redis-service.default.svc.cluster.local`
:6379
至此,我們的Redis集羣就真正創建完畢了,連到任意一個Redis Pod中檢驗一下:
root@k8s-node1 ~]# kubectl exec -it redis-app-2 /bin/bash
root@redis-app-2:/data# /usr/local/bin/redis-cli -c
127.0.0.1:6379> cluster nodes
c15f378a604ee5b200f06cc23e9371cbc04f4559192.168.169.197:6379@16379 master - 0 1526454835084 1 connected 10923-16383
96689f2018089173e528d3a71c4ef10af68ee462 192.168.169.204:6379@16379slave d884c4971de9748f99b10d14678d864187a9e5d3 0 1526454836491 4 connected
d884c4971de9748f99b10d14678d864187a9e5d3192.168.169.199:6379@16379 master - 0 1526454835487 4 connected 5462-10922
c3b4ae23c80ffe31b7b34ef29dd6f8d73beaf85f 192.168.169.198:6379@16379myself,master - 0 1526454835000 3 connected 0-5461
c8a8f70b4c29333de6039c47b2f3453ed11fb5c2192.168.169.201:6379@16379 slave c3b4ae23c80ffe31b7b34ef29dd6f8d73beaf85f 01526454836000 3 connected
237d46046d9b75a6822f02523ab894928e2300e6 192.168.169.200:6379@16379slave c15f378a604ee5b200f06cc23e9371cbc04f4559 0 1526454835000 1 connected
127.0.0.1:6379> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:4
...
省略
...
另外,還可以在NFS上查看Redis掛載的數據:
[root@k8s-node2 ~]
#ll /usr/local/k8s/redis/pv3/
總用量
8
-rw-r--r--.
1 nfsnobody nfsnobody
05月
1615:07 appendonly.aof
-rw-r--r--.
1 nfsnobody nfsnobody
1755月
1615:07 dump.rdb
-rw-r--r--.
1 nfsnobody nfsnobody
8175月
1616:55 nodes.conf
6.創建用於訪問Service
前面我們創建了用於實現StatefulSet的Headless Service,但該Service沒有Cluster Ip,因此不能用於外界訪問。所以,我們還需要創建一個Service,專用於爲Redis集羣提供訪問和負載均:
piVersion: v1
kind: Service
metadata:
name: redis-access-service
labels:
app: redis
spec:
ports:
- name: redis-port
protocol:
"TCP"
port: 6379
targetPort: 6379
selector:
app: redis
appCluster:redis-cluster
如上,該Service名稱爲 redis-access-service
,在K8S集羣中暴露6379端口,並且會對labels name
爲app: redis
或appCluster:redis-cluster
的pod進行負載均衡。
創建後查看:
[root@k8s-node1 redis]
#kubectl get svc redis-access-service -o wide
NAME TYPE
CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
redis-access-service ClusterIP
10.105.11.209 <none>
6379/TCP
41m app=redis,appCluster=redis-cluster
如上,在K8S集羣中,所有應用都可以通過10.105.11.209:6379
來訪問Redis集羣。當然,爲了方便測試,我們也可以爲Service添加一個NodePort映射到物理機上,這裏不再詳細介紹。
五、測試主從切換
在K8S上搭建完好Redis集羣后,我們最關心的就是其原有的高可用機制是否正常。這裏,我們可以任意挑選一個Master的Pod來測試集羣的主從切換機制,如redis-app-2
:
[root@k8s-node1 redis]
#kubectl get pods redis-app-2 -o wide
NAME READY STATUS RESTARTS AGE IP NODE
redis-app-
2
1/
1 Running
0
2h
192.168.
169.198 k8s-node2
進入redis-app-2
查看:
[root@k8s-node1 redis]
# kubectl exec -it redis-app-2 /bin/bash
root@redis-app-
2:/data#/usr/local/bin/redis-cli -c
127.0.0.1:6379> role
1)
"master"
2) (integer)
8666
3)
1)
1)
"192.168.169.201"
2)
"6379"
3)
"8666"
127.0.0.1:6379>
如上可以看到,其爲master,slave爲192.168.169.201
即
redis-app-5`。
接着,我們手動刪除redis-app-2
:
[root@k8s-node1 redis]
#kubectl delete pods redis-app-2
pod
"redis-app-2" deleted
[root@k8s-node1 redis]
#kubectl get pods redis-app-2 -o wide
NAME READY STATUS RESTARTS AGE IP NODE
redis-app-
2
1/
1 Running
0
20s
192.168.
169.210 k8s-node2
如上,IP改變爲192.168.169.210
。我們再進入redis-app-2
內部查看:
[root@k8s-node1 redis]
#kubectl exec -it redis-app-2 /bin/bash
root@redis-app-
2:/data#/usr/local/bin/redis-cli -c
127.0.0.1:6379> role
1)
"slave"
2)
"192.168.169.201"
3) (integer)
6379
4)
"connected"
5) (integer)
8960
127.0.0.1:6379>
如上,redis-app-2
變成了slave,從屬於它之前的從節點192.168.169.201
即redis-app-5
。
六、疑問
至此,大家可能會疑惑,前面講了這麼多似乎並沒有體現出StatefulSet的作用,其提供的穩定標誌redis-app-*
僅在初始化集羣的時候用到,而後續Redis Pod的通信或配置文件中並沒有使用該標誌。我想說,是的,本文使用StatefulSet部署Redis確實沒有體現出其優勢,還不如介紹Zookeeper集羣來的明顯,不過沒關係,學到知識就好。
那爲什麼沒有使用穩定的標誌,RedisPod也能正常進行故障轉移呢?這涉及了Redis本身的機制。因爲,Redis集羣中每個節點都有自己的NodeId(保存在自動生成的nodes.conf
中),並且該NodeId不會隨着IP的變化和變化,這其實也是一種固定的網絡標誌。也就是說,就算某個Redis Pod重啓了,該Pod依然會加載保存的NodeId來維持自己的身份。我們可以在NFS上查看redis-app-1
的nodes.conf
文件:
[root@k8s-node2 ~]
#cat /usr/local/k8s/redis/pv1/nodes.conf
96689f2018089173e528d3a71c4ef10af68ee462
192.168.
169.209:6379@16379 slave d884c4971de9748f99b10d14678d864187a9e5d3
015264609526514 connected
237d46046d9b75a6822f02523ab894928e2300e6
192.168.
169.200:6379@16379 slave c15f378a604ee5b200f06cc23e9371cbc04f4559
015264609526511 connected
c15f378a604ee5b200f06cc23e9371cbc04f4559
192.168.
169.197:6379@16379 master -
015264609526511 connected
10923-
16383
d884c4971de9748f99b10d14678d864187a9e5d3
192.168.
169.205:6379@16379 master -
015264609526514 connected
5462-
10922
c3b4ae23c80ffe31b7b34ef29dd6f8d73beaf85f
192.168.
169.198:6379@16379 myself,slavec8a8f70b4c29333de6039c47b2f3453ed11fb5c2
015264609525653 connected
c8a8f70b4c29333de6039c47b2f3453ed11fb5c2
192.168.
169.201:6379@16379 master -
015264609526516 connected
0-
5461
vars currentEpoch
6 lastVoteEpoch
4
如上,第一列爲NodeId,穩定不變;第二列爲IP和端口信息,可能會改變。
這裏,我們介紹NodeId的兩種使用場景:
當某個Slave Pod斷線重連後IP改變,但是Master發現其NodeId依舊, 就認爲該Slave還是之前的Slave。
當某個Master Pod下線後,集羣在其Slave中選舉重新的Master。待舊Master上線後,集羣發現其NodeId依舊,會讓舊Master變成新Master的slave。
對於這兩種場景,大家有興趣的話還可以自行測試,注意要觀察Redis的日誌。
7 從零開始搭建Kubernetes集羣(七、如何監控K8S集羣日誌)
一、前言
上一文《從零開始搭建Kubernetes集羣(六、在K8S上部署Redis集羣)》主要介紹瞭如何在K8S上部署一套基於StatefulSet的Redis集羣。本篇將介紹一下如何在K8S上進行日誌的監控。
二、架構選擇(ELK VS EFK)
ELK
我們首先介紹一下傳統的日誌監控方案。其中,ELK Stack 是我們最熟悉不過的架構。所謂ELK,分別指Elastic公司的Elasticsearch、Logstash、Kibana。在比較舊的ELK架構中,Logstash身兼日誌的採集、過濾兩職。但由於Logstash基於JVM,性能有一定限制,因此,目前業界更推薦使用Go語言開發FIiebeat代替Logstash的採集功能,Logstash只作爲了日誌過濾的中間件。
最常見的ELK架構如下:
image.png
如上圖所示,各角色功能如下:
多個Filebeat在各個業務端進行日誌採集,然後上傳至Logstash
多個Logstash節點並行(負載均衡,不作爲集羣),對日誌記錄進行過濾處理,然後上傳至Elasticsearch集羣
多個Elasticsearch構成集羣服務,提供日誌的索引和存儲能力
Kibana負責對Elasticsearch中的日誌數據進行檢索、分析
當然,在該架構中,根據業務特點,還可以加入某些中間件,如Redis、Kafak等:
image.png
如上圖所示,Kafka集羣作爲消息緩衝隊列,可以降低大量FIlebeat對Logstash的併發訪問壓力。
EFK
目前,在K8S的日誌監控解決方案中,EFK也是較常用的架構。所謂的EFK,即Elasticsearch + Fluentd + Kibana。在該架構中,Fluentd作爲日誌採集客戶端。但我個人認爲,相對於Filebeat,Fluentd並沒有突出的優勢。並且,由於同屬於Elastic公司,Filebeat可以更好的兼容其產品棧。因此,在K8S上,我仍然推薦ELK架構。
三、日誌採集方式
確定使用ELK+Filebeat
作爲架構後,我們還需要明確Filebeat採集K8S集羣日誌的方式,這也是本文的重點。官方文檔中提到了三種採集方式,這裏簡單介紹一下:
方式1:Node級日誌代理
在每個節點(即宿主機)上可以獨立運行一個Node級日誌代理,通常的實現方式爲DaemonSet。用戶應用只需要將日誌寫到標準輸出,Docker 的日誌驅動會將每個容器的標準輸出收集並寫入到主機文件系統,這樣Node級日誌代理就可以將日誌統一收集並上傳。另外,可以使用K8S的logrotate或Docker 的log-opt 選項負責日誌的輪轉。
image.png
Docker默認的日誌驅動(LogDriver)是json-driver,其會將日誌以JSON文件的方式存儲。所有容器輸出到控制檯的日誌,都會以*-json.log
的命名方式保存在/var/lib/docker/containers/
目錄下。對於Docker日誌驅動的具體介紹,請參考官方文檔。另外,除了收集Docker容器日誌,一般建議同時收集K8S自身的日誌以及宿主機的所有系統日誌,其位置都在var/log
下。
所以,簡單來說,本方式就是在每個node上各運行一個日誌代理容器,對本節點/var/log
和 /var/lib/docker/containers/
兩個目錄下的日誌進行採集,然後彙總到elasticsearch集羣,最後通過kibana展示。
方式2:伴生容器(sidecarcontainer)作爲日誌代理
創建一個伴生容器(也可稱作日誌容器),與應用程序容器在處於同一個Pod中。同時伴生容器內部運行一個獨立的、專門爲收集應用日誌的代理,常見的有Logstash、Fluentd 、Filebeat等。日誌容器通過共享卷可以獲得應用容器的日誌,然後進行上傳。
image.png
方式3:應用直接上傳日誌
應用程序容器直接通過網絡連接上傳日誌到後端,這是最簡單的方式。
image.png
對比
image.png
其中,相對來說,方式1在業界使用更爲廣泛,並且官方也更爲推薦。因此,最終我們採用ELK+Filebeat架構,並基於方式1,如下:
image.png
四、準備操作
DaemonSet概念介紹
在搭建前,我們先簡單介紹一下方式1中提到的DaemonSet,這也是一個重要的概念:
DaemonSet能夠讓所有(或者一些特定)的Node節點運行同一個pod。當節點加入到kubernetes集羣中,pod會被(DaemonSet)調度到該節點上運行,當節點從kubernetes集羣中被移除,被(DaemonSet)調度的pod會被移除,如果刪除DaemonSet,所有跟這個DaemonSet相關的pods都會被刪除。
因此,我們可以使用DaemonSet來部署Filebeat。這樣,每當集羣加入一個新的節點,該節點就會自動創建一個Filebeat守護進程,並有且只有一個。
另外,由於篇幅限制,本文只介紹如何通過基於DaemonSet的Filebeat來收集K8S集羣的日誌,而非介紹如何在K8S上搭建一個ELK集羣。同時,日誌記錄將直接上傳至Elasticsearch中,而不通過Logstash,並且本文假設Elasticsearch集羣已提前搭建完畢可直接使用。
清楚了本文的側重點後,好,走你~
官方Filebeat部署腳本介紹
這裏,我們將基於Elastic官方提供的Filebeat部署腳本進行部署,如下所示:
---
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-config
namespace: kube-system
labels:
k8s-app: filebeat
kubernetes.io/cluster-service:
"true"
data:
filebeat.yml: |-
filebeat.config:
prospectors:
# Mounted `filebeat-prospectors` configmap:
path:
${path.config}/prospectors.d/*.yml
# Reload prospectors configs as they change:
reload.enabled:
false
modules:
path:
${path.config}/modules.d/*.yml
# Reload module configs as they change:
reload.enabled:
false
processors:
- add_cloud_metadata:
cloud.id:
${ELASTIC_CLOUD_ID}
cloud.auth:
${ELASTIC_CLOUD_AUTH}
output.elasticsearch:
hosts: [
'${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']
username:
${ELASTICSEARCH_USERNAME}
password:
${ELASTICSEARCH_PASSWORD}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-prospectors
namespace: kube-system
labels:
k8s-app: filebeat
kubernetes.io/cluster-service:
"true"
data:
kubernetes.yml: |-
-
type: docker
containers.ids:
-
"*"
processors:
-add_kubernetes_metadata:
in_cluster:
true
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: filebeat
namespace: kube-system
labels:
k8s-app: filebeat
kubernetes.io/cluster-service:
"true"
spec:
template:
metadata:
labels:
k8s-app: filebeat
kubernetes.io/cluster-service:
"true"
spec:
serviceAccountName:filebeat
terminationGracePeriodSeconds: 30
containers:
- name: filebeat
image:docker.elastic.co/beats/filebeat:6.2.4
args: [
"-c",
"/etc/filebeat.yml",
"-e",
]
env:
- name:ELASTICSEARCH_HOST
value:elasticsearch
- name:ELASTICSEARCH_PORT
value:
"9200"
- name:ELASTICSEARCH_USERNAME
value: elastic
- name:ELASTICSEARCH_PASSWORD
value: changeme
- name:ELASTIC_CLOUD_ID
value:
- name: ELASTIC_CLOUD_AUTH
value:
securityContext:
runAsUser: 0
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 100Mi
volumeMounts:
- name: config
mountPath:/etc/filebeat.yml
readOnly:
true
subPath:filebeat.yml
- name: prospectors
mountPath:/usr/share/filebeat/prospectors.d
readOnly:
true
- name: data
mountPath: /usr/share/filebeat/data
- name:varlibdockercontainers
mountPath:/var/lib/docker/containers
readOnly:
true
volumes:
- name: config
configMap:
defaultMode: 0600
name:filebeat-config
- name:varlibdockercontainers
hostPath:
path:/var/lib/docker/containers
- name: prospectors
configMap:
defaultMode: 0600
name:filebeat-prospectors
- name: data
emptyDir: {}
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: filebeat
subjects:
- kind: ServiceAccount
name: filebeat
namespace: kube-system
roleRef:
kind: ClusterRole
name: filebeat
apiGroup:rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: filebeat
labels:
k8s-app: filebeat
rules:
- apiGroups: [
""]
# "" indicates the core API group
resources:
- namespaces
- pods
verbs:
- get
- watch
- list
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: filebeat
namespace: kube-system
labels:
k8s-app: filebeat
---
如上,看起來似乎挺複雜,可以分爲如下幾個部分:
ConfigMap
DaemonSet
ClusterRoleBinding
ClusterRole
ServiceAccount
很熟悉是吧,如果你還不清楚這些概念,請戳《從零開始搭建Kubernetes集羣(四、搭建K8S Dashboard)》。
ConfigMap
我們先重點關注一下DaemonSet的volumeMounts
和volumes
,以瞭解ConfigMap的掛載方式:
volumeMounts:
- name: config
mountPath:
/etc/filebeat.yml
readOnly:
true
subPath:filebeat.yml
- name: prospectors
mountPath:
/usr/share/filebeat/prospectors.d
readOnly:
true
- name: data
mountPath:
/usr/share/filebeat/data
- name:varlibdockercontainers
mountPath:
/var/lib/docker/containers
readOnly:
true
volumes:
- name: config
configMap:
defaultMode:
0600
name:filebeat-config
- name:varlibdockercontainers
hostPath:
path:
/var/lib/docker/containers
- name: prospectors
configMap:
defaultMode:
0600
name:filebeat-prospectors
- name: data
emptyDir: {}
如上,volumeMounts包括四個部分,解釋如下:
configfilebeat-config
這個Configmap會生成一個filebeat.yml
文件,其會被掛載爲Filebeat的配置文件/etc/filebeat.yml
prospectorsprospectors
這個Configmap會生成一個kubernetes.yml
文件,其會被掛載到路徑/usr/share/filebeat/prospectors.d
下,並被filebeat.yml
引用
data
Filebeat自身的數據掛載爲emptyDir: {}
varlibdockercontainers
K8S集羣的日誌都存儲在/var/lib/docker/containers
,Filebeat將從該路徑進行收集
瞭解了ConfigMap的掛載方式後,現在,我們分析第一個ConfigMap:
---
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-config
namespace: kube-system
labels:
k8s-app: filebeat
kubernetes.io/cluster-service:
"true"
data:
filebeat.yml: |-
filebeat.config:
prospectors:
# Mounted `filebeat-prospectors` configmap:
path:
${path.config}/prospectors.d/*.yml
# Reload prospectors configs as they change:
reload.enabled:
false
modules:
path:
${path.config}/modules.d/*.yml
# Reload module configs as they change:
reload.enabled:
false
processors:
- add_cloud_metadata:
cloud.id:
${ELASTIC_CLOUD_ID}
cloud.auth:
${ELASTIC_CLOUD_AUTH}
output.elasticsearch:
hosts: [
'${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']
username:
${ELASTICSEARCH_USERNAME}
password:
${ELASTICSEARCH_PASSWORD}
我們知道,Configmap的每個key都會生成一個同名的文件,因此這裏會創建一個配置文件filebeat.yml
文件,其內容中的環境變量將由DaemonSet中的env部分定義。
在filebeat.yml
中,可以看到Filebeat的一個重要組件: prospectors(採礦者),其主要用來指定從哪些文件中採集數據。這裏,prospectors並沒有直接指定目標文件,而是間接的引用路徑:${path.config}/prospectors.d/*.yml
,由前面可知,該路徑中的yml文件由第二個ConfigMap定義:
---
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-prospectors
namespace: kube-system
labels:
k8s-app: filebeat
kubernetes.io/cluster-service:
"true"
data:
kubernetes.yml: |-
-
type: docker
containers.ids:
-
"*"
processors:
-add_kubernetes_metadata:
in_cluster:
true
如上,type指定了prospectors的類型爲docker,表示收集本機的docker日誌。containers.ids
爲*
表示監聽所有容器。type除了docker,一般使用更多的是log,可以直接指定任何路徑上的日誌文件,參見官方文檔。
五、部署步驟
介紹完Filebeat的部署腳本後,我們開始真正的部署過程。
1.部署Filebeat
官方配置文件無法直接使用,需要我們定製。首先,修改DaemonSet中的環境變量env:
env:
-name:
ELASTICSEARCH_HOST
value: "
X.X.X.X"
-name:
ELASTICSEARCH_PORT
value: "9200"
-name:
ELASTICSEARCH_USERNAME
value:
-name:
ELASTICSEARCH_PASSWORD
value:
-name:
ELASTIC_CLOUD_ID
value:
-name:
ELASTIC_CLOUD_AUTH
value:
如上,ELASTICSEARCH_HOST
指定爲Elasticsearch集羣的入口地址,端口ELASTICSEARCH_PORT
爲默認的9200;由於我的集羣沒有加密,因此ELASTICSEARCH_USERNAME
和ELASTICSEARCH_PASSWORD
全部留空,大家可以酌情修改;其他保持默認。
同時,還需要註釋掉第一個ConfigMap中output.elasticsearch
的用戶名和密碼:
output.elasticsearch:
hosts: [
'${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']
#username: ${ELASTICSEARCH_USERNAME}
#password: ${ELASTICSEARCH_PASSWORD}
其次,還需要修改第二個ConfigMap的data部分爲:
data:
kubernetes.yml: |-
-
type:
log
enabled:
true
paths:
- /var/
log/*.
log
-
type: docker
containers.ids:
-
"*"
processors:
-add_kubernetes_metadata:
in_cluster:
true
如上,type: docker
的配置可以對K8S上所有Docker容器產生的日誌進行收集。另外,爲了收集宿主機系統日誌和K8S自身日誌,我們還需要獲取/var/log/*.log
。
修改並創建完畢後,查看DaemonSet信息,如下圖所示:
[root@k8s-node1 filebeat]# kubectl get ds -n kube-system
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
calico-etcd 1 1 1 1 1 node-role.kubernetes.io/master= 5d
calico-node 3 3 3 3 3
<none> 5d
filebeat 2 2 0 2 0
<none> 24s
kube-proxy 3 3 3 3 3
<none> 5d
查看pod信息,每個節點都會啓動一個filebeat容器:
filebeat-hr5vq 1/1 Running 1 3m 192.168.169.223 k8s-node2
filebeat-khzzj 1/1 Running 1 3m 192.168.108.7 k8s-node3
filebeat-rsnbl 1/1 Running 0 3m 192.168.36.126 k8s-node1
2.部署Kibana
參考官方示例,我們按需修改爲如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: kibana-logging
namespace: kube-system
labels:
k8s-app: kibana-logging
spec:
replicas:
1
selector:
matchLabels:
k8s-app:kibana-logging
template:
metadata:
labels:
k8s-app:kibana-logging
spec:
containers:
- name: kibana-logging
image:docker.elastic.co/kibana/kibana:
6.2.4
resources:
#need more cpu upon initialization, therefore burstable class
limits:
cpu:
1000m
requests:
cpu:
100m
env:
- name:ELASTICSEARCH_URL
value: http:
//X.X.X.X:9200
ports:
- containerPort:
5601
name: ui
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: kibana-logging
namespace: kube-system
labels:
k8s-app: kibana-logging
spec:
type: NodePort
ports:
- port:
5601
targetPort:
5601
selector:
k8s-app: kibana-logging
如上,Kibana的版本爲6.2.4,並且一定要與Filebeat、Elasticsearch保持一致。另外,注意將Deployment中env的環境變量ELASTICSEARCH_URL
,修改爲自己的Elasticsearch集羣地址。
這裏我們使用了Service暴露了NodePort,當然也可以使用Ingress,請參見《從零開始搭建Kubernetes集羣(五、搭建K8S Ingress)》。
3.訪問Kibana
好了,現在我們可以通過NodeIp:NodePort或Ingress方式來訪問Kibana。在配置Elasticsearch索引前綴後,即可檢索日誌:
image.png
如上,可以看到K8S中各個容器的日誌,當然也包括宿主機的系統日誌。
4.測試應用日誌
至此,我們通過Filebeat成功獲取了K8S上的容器日誌以及系統日誌。但在實際中,我們更關注的是應用程序的業務日誌。這裏,我們編寫一個簡單的JAVA項目來測試一下。
測試代碼
只是簡單的循環輸出遞增序列:
image.png
logback.xml
appender指定爲STDOUT即可:
image.png
Dockerfile
可以使用gradle將項目發佈爲tar包,然後拷貝到java:9-re
鏡像中。在build鏡像後,記得別忘記上傳至自己的倉庫中:
image.png
K8S部署腳本
執行該腳本即可完成測試項目的部署:
image.png
輸出日誌
我們可以去/var/lib/docker/containers/
下查看測試項目輸出的json格式日誌:
image.png
在Dashborad中,也可以查看標準輸出的日誌:
image.png
好了,我們已經成功的通過Filebeat上傳了自定義的應用程序日誌,收工~