kubernetes+docker虛擬機部署方案

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集羣(一、開篇)

從零開始搭建Kubernetes集羣(二、搭建虛擬機環境)

從零開始搭建Kubernetes集羣(三、搭建K8S集羣)

從零開始搭建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-node2k8s-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-minimalrules中清晰的列出了其擁有的多個權限。通過名稱我們可以猜到,這個權限級別是比較低的。

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-minimalsubjects中爲綁定的用戶: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中的kindname,使用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-IPNone,表示這是一個“無頭”服務。

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 nameapp: redisappCluster: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.201redis-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.201redis-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-1nodes.conf文件:

[root@k8s-node2 ~]#cat /usr/local/k8s/redis/pv1/nodes.conf

96689f2018089173e528d3a71c4ef10af68ee462192.168.169.209:6379@16379 slave d884c4971de9748f99b10d14678d864187a9e5d3 015264609526514 connected

237d46046d9b75a6822f02523ab894928e2300e6192.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的volumeMountsvolumes,以瞭解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包括四個部分,解釋如下:

config
filebeat-config這個Configmap會生成一個filebeat.yml文件,其會被掛載爲Filebeat的配置文件/etc/filebeat.yml

prospectors
prospectors這個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_USERNAMEELASTICSEARCH_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上傳了自定義的應用程序日誌,收工~

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章