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上传了自定义的应用程序日志,收工~

 

Logo

开源、云原生的融合云平台

更多推荐