提交 dcb5c2df 编写于 作者: 阳明的博客's avatar 阳明的博客

[add] 添加 prometheus operator文章

上级 ba3cc39c
......@@ -85,4 +85,6 @@
* [监控 Kubernetes 集群应用](docs/53.监控Kubernetes集群应用.md)
* [监控 Kubernetes 集群节点](docs/54.监控Kubernetes集群节点.md)
* [监控 Kubernetes 常用资源对象](docs/55.监控Kubernetes常用资源对象.md)
* [Grafana 的安装使用](docs/56.Grafana的安装使用.md)
\ No newline at end of file
* [Grafana 的安装使用](docs/56.Grafana的安装使用.md)
* [AlertManager 的使用](docs/57.AlertManager的使用.md)
* [Prometheus Operator 的安装](docs/58.Prometheus Operator.md)
apiVersion: v1
kind: Service
metadata:
name: ngxdemo
spec:
selector:
app: app
type: NodePort
ports:
- name: http
protocol: TCP
port: 80
targetPort: nginxweb
dingtalkserv @ 36065de3
Subproject commit 36065de354bb589f08d1e1a5631804ce1d08b54f
......@@ -323,7 +323,7 @@ CMD [ "npm", "start" ]
把这个 Dockerfile 放到 Node.js 项目的根目录,构建好镜像后,就可以直接拿来启动容器运行。但是如果我们还有第二个 Node.js 项目也差不多呢?好吧,那就再把这个 Dockerfile 复制到第二个项目里。那如果有第三个项目呢?再复制么?文件的副本越多,版本控制就越困难,让我们继续看这样的场景维护的问题:
如果第一个 Node.js 项目在开发过程中,发现这个 Dockerfile 里存在问题,比如敲错字了、或者需要安装额外的包,然后开发人员修复了这个 Dockerfile,再次构建,问题解决。第一个项目没问题了,但是第二个项目呢?虽然最初 Dockerfile 是复制、粘贴自第一个项目的,但是并不会因为第一个项目修复了他们的 Dockerfile,而第二个项目的 Dockerfile 就会被自动修复。
如果第一个 Node.js 项目在开发过程中,发现这个 Dockerfile 里存在问题,比如敲错字了、或者需要安装额外的包,然后开发人员修复了这个 Dockerfile,再次构建,问题解决。第一个项目没问题了,但是第二个项目呢?虽然最初 Dockerfile 是复制、粘贴自第一个项目的,但是并不会因为第一个项目修复了他们的 Dockerfile,而第二个项目的 Dockerfile 就会被自动修复。
那么我们可不可以做一个基础镜像,然后各个项目使用这个基础镜像呢?这样基础镜像更新,各个项目不用同步 Dockerfile 的变化,重新构建后就继承了基础镜像的更新?好吧,可以,让我们看看这样的结果。那么上面的这个 Dockerfile 就会变为:
```docker
......
......@@ -45,7 +45,7 @@ Kubernetes 多组件之间的通信原理:
* controller manager、scheduler、kube-proxy 和 kubelet 等均通过 apiserver watch API 监测资源变化情况,并对资源作相应的操作
* 所有需要更新资源状态的操作均通过 apiserver 的 REST API 进行
* apiserver 也会直接调用 kubelet API(如 logs, exec, attach 等),默认不校验 kubelet 证书,但可以通过 `--kubelet-certificate-authority` 开启(而 GKE 通过 SSH 隧道保护它们之间的通信)
* apiserver 也会直接调用 kubelet API(如 logs, exec, attach 等),默认不校验 kubelet 证书,但可以通过 `--kubelet-certificate-authority` 开启(而 GKE 通过 SSH 隧道保护它们之间的通信)
比如最典型的创建 Pod 的流程:
​​![k8s pod](./images/k8s-pod-process.png)
......
......@@ -7,15 +7,15 @@
是不是感觉`Init Container`和之前的钩子函数有点类似啊,只是是在容器执行前来做一些工作,是吧?从直观的角度看上去的话,初始化容器的确有点像`PreStart`,但是钩子函数和我们的`Init Container`是处在不同的阶段的,我们可以通过下面的图来了解下:
![loap](./images/loap.jpg)
从上面这张图我们可以直观的看到`PostStart``PreStop`包括`liveness``readiness`是属于主容器的生命周期范围内的,而`Init Container`是独立于主容器之外的,当然他们都属于`Pod`的生命周期范畴之内的,现在我们应该明白`Init Container`和钩子函数之类的区别了吧。
从上面这张图我们可以直观的看到`PostStart``PreStop`包括`liveness``readiness`是属于主容器的生命周期范围内的,而`Init Container`是独立于主容器之外的,当然他们都属于`Pod`的生命周期范畴之内的,现在我们应该明白`Init Container`和钩子函数之类的区别了吧。
另外我们可以看到上面我们的`Pod`右边还有一个`infra`的容器,这是一个什么容器呢?我们可以在集群环境中去查看下人任意一个`Pod`对应的运行的`Docker`容器,我们可以发现每一个`Pod`下面都包含了一个`pause-amd64`的镜像,这个就是我们的`infra`镜像,我们知道`Pod`下面的所有容器是共享同一个网络命名空间的,这个镜像就是来做这个事情的,所以每一个`Pod`当中都会包含一个这个镜像。
另外我们可以看到上面我们的`Pod`右边还有一个`infra`的容器,这是一个什么容器呢?我们可以在集群环境中去查看下人任意一个`Pod`对应的运行的`Docker`容器,我们可以发现每一个`Pod`下面都包含了一个`pause-amd64`的镜像,这个就是我们的`infra`镜像,我们知道`Pod`下面的所有容器是共享同一个网络命名空间的,这个镜像就是来做这个事情的,所以每一个`Pod`当中都会包含一个这个镜像。
> 很多同学最开始 Pod 启动不起来就是因为这个 infra 镜像没有被拉下来,因为默认该镜像是需要到谷歌服务器上拉取的,所以需要提前拉取到节点上面。
> 很多同学最开始 Pod 启动不起来就是因为这个 infra 镜像没有被拉下来,因为默认该镜像是需要到谷歌服务器上拉取的,所以需要提前拉取到节点上面。
我们说`Init Container`主要是来做初始化容器工作的,那么他有哪些应用场景呢?
* 等待其他模块Ready:这个可以用来解决服务之间的依赖问题,比如我们有一个 Web 服务,该服务又依赖于另外一个数据库服务,但是在我们启动这个 Web 服务的时候我们并不能保证依赖的这个数据库服务就已经启动起来了,所以可能会出现一段时间内 Web 服务连接数据库异常。要解决这个问题的话我们就可以在 Web 服务的 Pod 中使用一个 InitContainer,在这个初始化容器中去检查数据库是否已经准备好了,准备好了过后初始化容器就结束退出,然后我们的主容器 Web 服务被启动起来,这个时候去连接数据库就不会有问题了。
* 等待其他模块Ready:这个可以用来解决服务之间的依赖问题,比如我们有一个 Web 服务,该服务又依赖于另外一个数据库服务,但是在我们启动这个 Web 服务的时候我们并不能保证依赖的这个数据库服务就已经启动起来了,所以可能会出现一段时间内 Web 服务连接数据库异常。要解决这个问题的话我们就可以在 Web 服务的 Pod 中使用一个 InitContainer,在这个初始化容器中去检查数据库是否已经准备好了,准备好了过后初始化容器就结束退出,然后我们的主容器 Web 服务被启动起来,这个时候去连接数据库就不会有问题了。
* 做初始化配置:比如集群里检测所有已经存在的成员节点,为主容器准备好集群的配置信息,这样主容器起来后就能用这个配置信息加入集群。
* 其它场景:如将 pod 注册到一个中央数据库、配置中心等。
......@@ -108,7 +108,7 @@ spec:
初始化容器执行完,会下载一个 html 文件映射到emptyDir{},而主容器也是和 spec.volumes 里的emptyDir{} 进行映射,所以`nginx容器的`/usr/share/nginx/html`目录下会映射 index.html 文件。
我们来创建下该`Pod`,然后验证nginx容器是否运行:
我们来创建下该`Pod`,然后验证nginx容器是否运行:
```shell
$ kubectl get pod init-demo
```
......
......@@ -5,11 +5,11 @@
遇到这样的问题该怎么解决呢?在没有使用`Kubernetes`之前,我相信可能很多同学都遇到过这样的问题,不一定是`IP`变化的问题,比如我们在部署一个`WEB`服务的时候,前端一般部署一个`Nginx`作为服务的入口,然后`Nginx`后面肯定就是挂载的这个服务的大量后端,很早以前我们可能是去手动更改`Nginx`配置中的`upstream`选项,来动态改变提供服务的数量,到后面出现了一些`服务发现`的工具,比如`Consul``ZooKeeper`还有我们熟悉的`etcd`等工具,有了这些工具过后我们就可以只需要把我们的服务注册到这些服务发现中心去就可以,然后让这些工具动态的去更新`Nginx`的配置就可以了,我们完全不用去手工的操作了,是不是非常方便。
![nginx](./images/nginx-consul.png)
同样的,要解决我们上面遇到的问题是不是实现一个服务发现的工具也可以解决啊?没错的,当我们`Pod`被销毁或者新建过后,我们可以把这个`Pod`的地址注册到这个服务发现中心去就可以,但是这样的话我们的前端的`Pod`结合就不能直接去连接后台的`Pod`集合了是吧,应该连接到一个能够做服务发现的中间件上面,对吧?
同样的,要解决我们上面遇到的问题是不是实现一个服务发现的工具也可以解决啊?没错的,当我们`Pod`被销毁或者新建过后,我们可以把这个`Pod`的地址注册到这个服务发现中心去就可以,但是这样的话我们的前端的`Pod`结合就不能直接去连接后台的`Pod`集合了是吧,应该连接到一个能够做服务发现的中间件上面,对吧?
没错,`Kubernetes`集群就为我们提供了这样的一个对象 - `Service``Service`是一种抽象的对象,它定义了一组`Pod`的逻辑集合和一个用于访问它们的策略,其实这个概念和微服务非常类似。一个`Serivce`下面包含的`Pod`集合一般是由`Label Selector`来决定的。
没错,`Kubernetes`集群就为我们提供了这样的一个对象 - `Service``Service`是一种抽象的对象,它定义了一组`Pod`的逻辑集合和一个用于访问它们的策略,其实这个概念和微服务非常类似。一个`Serivce`下面包含的`Pod`集合一般是由`Label Selector`来决定的。
比如我们上面的例子,假如我们后端运行了3个副本,这些副本都是可以替代的,因为前端并不关心它们使用的是哪一个后端服务。尽管由于各种原因后端的`Pod`集合会发送变化,但是前端却不需要知道这些变化,也不需要自己用一个列表来记录这些后端的服务,`Service`的这种抽象就可以帮我们达到这种解耦的目的。
比如我们上面的例子,假如我们后端运行了3个副本,这些副本都是可以替代的,因为前端并不关心它们使用的是哪一个后端服务。尽管由于各种原因后端的`Pod`集合会发送变化,但是前端却不需要知道这些变化,也不需要自己用一个列表来记录这些后端的服务,`Service`的这种抽象就可以帮我们达到这种解耦的目的。
## 三种IP
......@@ -19,21 +19,21 @@
* Pod IP: `Pod`的IP地址
* Cluster IP: `Service``IP`地址
首先,`Node IP``Kubernetes`集群中节点的物理网卡`IP`地址(一般为内网),所有属于这个网络的服务器之间都可以直接通信,所以`Kubernetes`集群外要想访问`Kubernetes`集群内部的某个节点或者服务,肯定得通过`Node IP`进行通信(这个时候一般是通过外网`IP`了)
首先,`Node IP``Kubernetes`集群中节点的物理网卡`IP`地址(一般为内网),所有属于这个网络的服务器之间都可以直接通信,所以`Kubernetes`集群外要想访问`Kubernetes`集群内部的某个节点或者服务,肯定得通过`Node IP`进行通信(这个时候一般是通过外网`IP`了)
然后`Pod IP`是每个`Pod``IP`地址,它是`Docker Engine`根据`docker0`网桥的`IP`地址段进行分配的(我们这里使用的是`flannel`这种网络插件保证所有节点的`Pod IP`不会冲突)
然后`Pod IP`是每个`Pod``IP`地址,它是`Docker Engine`根据`docker0`网桥的`IP`地址段进行分配的(我们这里使用的是`flannel`这种网络插件保证所有节点的`Pod IP`不会冲突)
最后`Cluster IP`是一个虚拟的`IP`,仅仅作用于`Kubernetes Service`这个对象,由`Kubernetes`自己来进行管理和分配地址,当然我们也无法`ping`这个地址,他没有一个真正的实体对象来响应,他只能结合`Service Port`来组成一个可以通信的服务。
## 定义Service
## 定义Service
定义`Service`的方式和我们前面定义的各种资源对象的方式类型,例如,假定我们有一组`Pod`服务,它们对外暴露了 8080 端口,同时都被打上了`app=myapp`这样的标签,那么我们就可以像下面这样来定义一个`Service`对象:
定义`Service`的方式和我们前面定义的各种资源对象的方式类型,例如,假定我们有一组`Pod`服务,它们对外暴露了 8080 端口,同时都被打上了`app=myapp`这样的标签,那么我们就可以像下面这样来定义一个`Service`对象:
```yaml
apiVersion: v1
kind: Service
metadata:
metadata:
name: myservice
spec:
selector:
......@@ -44,16 +44,16 @@ spec:
targetPort: 8080
name: myapp-http
```
然后通过的使用`kubectl create -f myservice.yaml`就可以创建一个名为`myservice``Service`对象,它会将请求代理到使用 TCP 端口为 8080,具有标签`app=myapp``Pod`上,这个`Service`会被系统分配一个我们上面说的`Cluster IP`,该`Service`还会持续的监听`selector`下面的`Pod`,会把这些`Pod`信息更新到一个名为`myservice``Endpoints`对象上去,这个对象就类似于我们上面说的`Pod`集合了。
然后通过的使用`kubectl create -f myservice.yaml`就可以创建一个名为`myservice``Service`对象,它会将请求代理到使用 TCP 端口为 8080,具有标签`app=myapp``Pod`上,这个`Service`会被系统分配一个我们上面说的`Cluster IP`,该`Service`还会持续的监听`selector`下面的`Pod`,会把这些`Pod`信息更新到一个名为`myservice``Endpoints`对象上去,这个对象就类似于我们上面说的`Pod`集合了。
需要注意的是,`Service`能够将一个接收端口映射到任意的`targetPort`。 默认情况下,`targetPort`将被设置为与`port`字段相同的值。 可能更有趣的是,targetPort 可以是一个字符串,引用了 backend Pod 的一个端口的名称。 因实际指派给该端口名称的端口号,在每个 backend Pod 中可能并不相同,所以对于部署和设计 Service ,这种方式会提供更大的灵活性。
需要注意的是,`Service`能够将一个接收端口映射到任意的`targetPort`。 默认情况下,`targetPort`将被设置为与`port`字段相同的值。 可能更有趣的是,targetPort 可以是一个字符串,引用了 backend Pod 的一个端口的名称。 因实际指派给该端口名称的端口号,在每个 backend Pod 中可能并不相同,所以对于部署和设计 Service ,这种方式会提供更大的灵活性。
另外`Service`能够支持 TCP 和 UDP 协议,默认是 TCP 协议。
## kube-proxy
前面我们讲到过,在`Kubernetes`集群中,每个`Node`会运行一个`kube-proxy`进程, 负责为`Service`实现一种 VIP(虚拟 IP,就是我们上面说的`clusterIP`)的代理形式,现在的`Kubernetes`中默认是使用的`iptables`这种模式来代理。这种模式,`kube-proxy`会监视`Kubernetes master`对 Service 对象和 Endpoints 对象的添加和移除。 对每个 Service,它会添加上 iptables 规则,从而捕获到达该 Service 的 clusterIP(虚拟 IP)和端口的请求,进而将请求重定向到 Service 的一组 backend 中的某一个个上面。 对于每个 Endpoints 对象,它也会安装 iptables 规则,这个规则会选择一个 backend Pod。
前面我们讲到过,在`Kubernetes`集群中,每个`Node`会运行一个`kube-proxy`进程, 负责为`Service`实现一种 VIP(虚拟 IP,就是我们上面说的`clusterIP`)的代理形式,现在的`Kubernetes`中默认是使用的`iptables`这种模式来代理。这种模式,`kube-proxy`会监视`Kubernetes master`对 Service 对象和 Endpoints 对象的添加和移除。 对每个 Service,它会添加上 iptables 规则,从而捕获到达该 Service 的 clusterIP(虚拟 IP)和端口的请求,进而将请求重定向到 Service 的一组 backend 中的某一个个上面。 对于每个 Endpoints 对象,它也会安装 iptables 规则,这个规则会选择一个 backend Pod。
默认的策略是,随机选择一个 backend。 我们也可以实现基于客户端 IP 的会话亲和性,可以将 `service.spec.sessionAffinity` 的值设置为 "ClientIP" (默认值为 "None")。
......@@ -84,7 +84,7 @@ spec:
```yaml
apiVersion: v1
kind: Service
metadata:
metadata:
name: myservice
spec:
selector:
......
......@@ -244,7 +244,7 @@ wordpress NodePort 10.101.7.69 <none> 80:32255/TCP 1m
可以看到`wordpress`服务产生了一个32255的端口,现在我们是不是就可以通过任意节点的`NodeIP`加上32255端口,就可以访问我们的`wordpress`应用了,在浏览器中打开,如果看到`wordpress`跳转到了安装页面,证明我们的嗯安装是没有任何问题的了,如果没有出现预期的效果,那么就需要去查看下`Pod`的日志来查看问题了:
![wordpress](images/wordpress.jpg)
然后根据页面提示,填上对应的信息,点击“安装”即可,最终安装成功后,我们就可以看到熟悉的首页界面了:
然后根据页面提示,填上对应的信息,点击“安装”即可,最终安装成功后,我们就可以看到熟悉的首页界面了:
![wordpress-home](images/wordpress-home.jpg)
......
......@@ -42,7 +42,7 @@ spec:
port: 8080
initialDelaySeconds: 60
timeoutSeconds: 5
failureThreshold: 12
failureThreshold: 12
readinessProbe:
httpGet:
path: /login
......@@ -68,7 +68,7 @@ spec:
- name: jenkinshome
persistentVolumeClaim:
claimName: opspvc
---
apiVersion: v1
kind: Service
......@@ -88,7 +88,7 @@ spec:
nodePort: 30002
- name: agent
port: 50000
targetPort: agent
targetPort: agent
```
为了方便演示,我们把本节课所有的对象资源都放置在一个名为 kube-ops 的 namespace 下面,所以我们需要添加创建一个 namespace:
......@@ -292,38 +292,38 @@ Jenkins 安装完成了,接下来我们不用急着就去使用,我们要了
![kubernetes plugin](./images/setup-jenkins-k8s-plugin.png)
第2步. 安装完毕后,点击 Manage Jenkins —> Configure System —> (拖到最下方)Add a new cloud —> 选择 Kubernetes,然后填写 Kubernetes 和 Jenkins 配置信息。
第2步. 安装完毕后,点击 Manage Jenkins —> Configure System —> (拖到最下方)Add a new cloud —> 选择 Kubernetes,然后填写 Kubernetes 和 Jenkins 配置信息。
![kubernetes plugin config1](./images/jenkins-k8s-config1.jpg)
注意 namespace,我们这里填 kube-ops,然后点击**Test Connection**,如果出现 Connection test successful 的提示信息证明 Jenkins 已经可以和 Kubernetes 系统正常通信了,然后下方的 Jenkins URL 地址:**http://jenkins2.kube-ops.svc.cluster.local:8080**,这里的格式为:服务名.namespace.svc.cluster.local:8080,**根据上面创建的jenkins 的服务名填写,我这里是之前创建的名为jenkins,如果是用上面我们创建的就应该是jenkins2**
注意 namespace,我们这里填 kube-ops,然后点击**Test Connection**,如果出现 Connection test successful 的提示信息证明 Jenkins 已经可以和 Kubernetes 系统正常通信了,然后下方的 Jenkins URL 地址:**http://jenkins2.kube-ops.svc.cluster.local:8080**,这里的格式为:服务名.namespace.svc.cluster.local:8080,**根据上面创建的jenkins 的服务名填写,我这里是之前创建的名为jenkins,如果是用上面我们创建的就应该是jenkins2**
> 另外需要注意,如果这里 Test Connection 失败的话,很有可能是权限问题,这里就需要把我们创建的 jenkins 的 serviceAccount 对应的 secret 添加到这里的 Credentials 里面。
> 另外需要注意,如果这里 Test Connection 失败的话,很有可能是权限问题,这里就需要把我们创建的 jenkins 的 serviceAccount 对应的 secret 添加到这里的 Credentials 里面。
第3步. 配置 Pod Template,其实就是配置 Jenkins Slave 运行的 Pod 模板,命名空间我们同样是用 kube-ops,Labels 这里也非常重要,对于后面执行 Job 的时候需要用到该值,然后我们这里使用的是 cnych/jenkins:jnlp 这个镜像,这个镜像是在官方的 jnlp 镜像基础上定制的,加入了 kubectl 等一些实用的工具。
第3步. 配置 Pod Template,其实就是配置 Jenkins Slave 运行的 Pod 模板,命名空间我们同样是用 kube-ops,Labels 这里也非常重要,对于后面执行 Job 的时候需要用到该值,然后我们这里使用的是 cnych/jenkins:jnlp 这个镜像,这个镜像是在官方的 jnlp 镜像基础上定制的,加入了 kubectl 等一些实用的工具。
![kubernetes plugin config2](./images/jenkins-k8s-config2.png)
另外需要注意我们这里需要在下面挂载两个主机目录,一个是 /var/run/docker.sock,该文件是用于 Pod 中的容器能够共享宿主机的 Docker,这就是大家说的 docker in docker 的方式,Docker 二进制文件我们已经打包到上面的镜像中了,另外一个目录下 /root/.kube 目录,我们将这个目录挂载到容器的 /home/jenkins/.kube 目录下面这是为了让我们能够在 Pod 的容器中能够使用 kubectl 工具来访问我们的 Kubernetes 集群,方便我们后面在 Slave Pod 部署 Kubernetes 应用。
另外需要注意我们这里需要在下面挂载两个主机目录,一个是 /var/run/docker.sock,该文件是用于 Pod 中的容器能够共享宿主机的 Docker,这就是大家说的 docker in docker 的方式,Docker 二进制文件我们已经打包到上面的镜像中了,另外一个目录下 /root/.kube 目录,我们将这个目录挂载到容器的 /home/jenkins/.kube 目录下面这是为了让我们能够在 Pod 的容器中能够使用 kubectl 工具来访问我们的 Kubernetes 集群,方便我们后面在 Slave Pod 部署 Kubernetes 应用。
![kubernetes plugin config3](./images/jenkins-k8s-config3.png)
另外还有几个参数需要注意,如下图中的**Time in minutes to retain slave when idle**,这个参数表示的意思是当处于空闲状态的时候保留 Slave Pod 多长时间,这个参数最好我们保存默认就行了,如果你设置过大的话,Job 任务执行完成后,对应的 Slave Pod 就不会立即被销毁删除。
另外还有几个参数需要注意,如下图中的**Time in minutes to retain slave when idle**,这个参数表示的意思是当处于空闲状态的时候保留 Slave Pod 多长时间,这个参数最好我们保存默认就行了,如果你设置过大的话,Job 任务执行完成后,对应的 Slave Pod 就不会立即被销毁删除。
![kubernetes plugin config4](./images/jenkins-k8s-config4.png)
到这里我们的 Kubernetes Plugin 插件就算配置完成了。
到这里我们的 Kubernetes Plugin 插件就算配置完成了。
## 测试
Kubernetes 插件的配置工作完成了,接下来我们就来添加一个 Job 任务,看是否能够在 Slave Pod 中执行,任务执行完成后看 Pod 是否会被销毁。
Kubernetes 插件的配置工作完成了,接下来我们就来添加一个 Job 任务,看是否能够在 Slave Pod 中执行,任务执行完成后看 Pod 是否会被销毁。
在 Jenkins 首页点击**create new jobs**,创建一个测试的任务,输入任务名称,然后我们选择 Freestyle project 类型的任务:
![jenkins demo](./images/jenkins-demo1.png)
注意在下面的 Label Expression 这里要填入**haimaxy-jnlp**,就是前面我们配置的 Slave Pod 中的 Label,这两个地方必须保持一致
注意在下面的 Label Expression 这里要填入**haimaxy-jnlp**,就是前面我们配置的 Slave Pod 中的 Label,这两个地方必须保持一致
![config](./images/jenkins-demo1-config.jpeg)
然后往下拉,在 Build 区域选择**Execute shell**
然后往下拉,在 Build 区域选择**Execute shell**
![Build](./images/jenkins-demo1-config2.jpeg)
然后输入我们测试命令
......@@ -339,7 +339,7 @@ kubectl get pods
![command](./images/jenkins-demo1-config3.jpeg)
现在我们直接在页面点击做成的 Build now 触发构建即可,然后观察 Kubernetes 集群中 Pod 的变化
现在我们直接在页面点击做成的 Build now 触发构建即可,然后观察 Kubernetes 集群中 Pod 的变化
```shell
$ kubectl get pods -n kube-ops
NAME READY STATUS RESTARTS AGE
......
此差异已折叠。
......@@ -72,7 +72,7 @@ BlueOcean 可以安装在现有的 Jenkins 环境中,也可以使用 Docker
![blue demo3](./images/blue-demo3.png)
然后将生成的 token 填入下面的创建 Pipeline 的流程中,然后我们就有权限选择自己的仓库,包括下面需要构建的仓库,比如我们这里需要构建的是 jenkins-demo 这个仓库,然后创建 Pipeline 即可:
然后将生成的 token 填入下面的创建 Pipeline 的流程中,然后我们就有权限选择自己的仓库,包括下面需要构建的仓库,比如我们这里需要构建的是 jenkins-demo 这个仓库,然后创建 Pipeline 即可:
![blue demo4](./images/blue-demo4.png)
......@@ -84,7 +84,7 @@ Blue Ocean 会自动扫描仓库中的每个分支,会为根文件夹中包含
![blue demo6](./images/blue-demo6.png)
在上面的图中每个阶段我们都可以点击进去查看对应的构建结果,比如我们可以查看 Push 阶段下面的日志信息:
在上面的图中每个阶段我们都可以点击进去查看对应的构建结果,比如我们可以查看 Push 阶段下面的日志信息:
```shell
...
[jenkins-demo_dev-I2WMFUIFQCIFGRPNHN3HU7IZIMHEQMHWPUN2TP6DCYSWHFFFFHOA] Running shell script
......@@ -95,7 +95,7 @@ The push refers to a repository [docker.io/****/jenkins-demo]
...
```
我们可以看到本次构建的 Docker 镜像的 Tag 为**dev-ee90aa5**,是符合我们在 Jenkinsfile 中的定义的吧
我们可以看到本次构建的 Docker 镜像的 Tag 为**dev-ee90aa5**,是符合我们在 Jenkinsfile 中的定义的吧
现在我们更改下 k8s.yaml 将 环境变量的值的标记改成 BRANCH_NAME,当然 Jenkinsfile 也要对应的更改,然后提交代码到 dev 分支并且 push 到 Github 仓库,我们可以看到 Jenkins Blue Ocean 里面自动触发了一次构建工作,最好同样我们可以看到本次构建能够正常完成,最后我们查看下本次构建的结果:
```shell
......@@ -108,7 +108,7 @@ $ kubectl logs jenkins-demo-648876568d-q5mbx
Hello, Kubernetes!I'm from Jenkins CI!
BRANCH: dev
```
我们可以看到打印了一句 BRANCH: dev ,证明我本次 CI/CD 是正常的。
我们可以看到打印了一句 BRANCH: dev ,证明我本次 CI/CD 是正常的。
现在我们来把 dev 分支的代码合并到 master 分支,然后来触发一次自动构建:
```shell
......@@ -140,11 +140,11 @@ To git@github.com:cnych/jenkins-demo.git
...
```
我们可以查看到此处推送的镜像 TAG 为 ee90aa5,没有分支的前缀,是不是和我们前面在 Jenkinsfile 中的定义是一致的,镜像推送完成后,进入 Deploy 阶段的时候我们可以看到出现了一个暂停的操作,让我们选择是否需要部署到线上,我们前面是不是定义的如果是 master 分支的话,在部署的阶段需要我们人工确认:
我们可以查看到此处推送的镜像 TAG 为 ee90aa5,没有分支的前缀,是不是和我们前面在 Jenkinsfile 中的定义是一致的,镜像推送完成后,进入 Deploy 阶段的时候我们可以看到出现了一个暂停的操作,让我们选择是否需要部署到线上,我们前面是不是定义的如果是 master 分支的话,在部署的阶段需要我们人工确认:
![bule demo7](./images/blue-demo7.png)
然后我们点击**Proceed**才会继续后面的部署工作,确认后,我们同样可以去 Kubernetes 环境中查看下部署的结果:
然后我们点击**Proceed**才会继续后面的部署工作,确认后,我们同样可以去 Kubernetes 环境中查看下部署的结果:
```shell
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
......@@ -156,15 +156,15 @@ Hello, Kubernetes!I'm from Jenkins CI!
BRANCH: master
```
现在我们可以看到打印出来的信息是 master,证明部署是没有问题的。
现在我们可以看到打印出来的信息是 master,证明部署是没有问题的。
到这里我们就实现了多分支代码仓库的完整的 CI/CD 流程。
到这里我们就实现了多分支代码仓库的完整的 CI/CD 流程。
当然我们这里的示例还是太简单,只是单纯为了说明 CI/CD 的步骤,在后面的课程中,我们会结合其他的工具进一步对我们现有的方式进行改造,比如使用 Helm、Gitlab 等等。
另外如果你对声明式的 Pipeline 比较熟悉的话,我们推荐使用这种方式来编写 Jenkinsfile 文件,因为使用声明式的方式编写的 Jenkinsfile 文件在 Blue Ocean 中不但支持得非常好,我们还可以直接在 Blue Ocean Editor 中可视化的对我们的 Pipeline 进行编辑操作,非常方便。
另外如果你对声明式的 Pipeline 比较熟悉的话,我们推荐使用这种方式来编写 Jenkinsfile 文件,因为使用声明式的方式编写的 Jenkinsfile 文件在 Blue Ocean 中不但支持得非常好,我们还可以直接在 Blue Ocean Editor 中可视化的对我们的 Pipeline 进行编辑操作,非常方便。
> 我们会在单独的课程中对 Jenkinsfile 的声明式语法进行详细讲解,感兴趣的同学可以关注下。
......@@ -2,7 +2,7 @@
前面我们给大家讲解了 Service 的用法,我们可以通过 Service 生成的 ClusterIP(VIP)来访问 Pod 提供的服务,但是在使用的时候还有一个问题:我们怎么知道某个应用的 VIP 呢?比如我们有两个应用,一个是 api 应用,一个是 db 应用,两个应用都是通过 Deployment 进行管理的,并且都通过 Service 暴露出了端口提供服务。api 需要连接到 db 这个应用,我们只知道 db 应用的名称和 db 对应的 Service 的名称,但是并不知道它的 VIP 地址,我们前面的 Service 课程中是不是学习到我们通过 ClusterIP 就可以访问到后面的 Pod 服务,如果我们知道了 VIP 的地址是不是就行了?
## apiserver
我们知道可以从 apiserver 中直接查询获取到对应 service 的后端 Endpoints信息,所以最简单的办法是从 apiserver 中直接查询,如果偶尔一个特殊的应用,我们通过 apiserver 去查询到 Service 后面的 Endpoints 直接使用是没问题的,但是如果每个应用都在启动的时候去查询依赖的服务,这不但增加了应用的复杂度,这也导致了我们的应用需要依赖 Kubernetes 了,耦合度太高了,不具有通用性。
我们知道可以从 apiserver 中直接查询获取到对应 service 的后端 Endpoints信息,所以最简单的办法是从 apiserver 中直接查询,如果偶尔一个特殊的应用,我们通过 apiserver 去查询到 Service 后面的 Endpoints 直接使用是没问题的,但是如果每个应用都在启动的时候去查询依赖的服务,这不但增加了应用的复杂度,这也导致了我们的应用需要依赖 Kubernetes 了,耦合度太高了,不具有通用性。
## 环境变量
......@@ -60,9 +60,9 @@ nginx-service ClusterIP 10.107.225.42 <none> 5000/TCP 1m
...
```
我们可以看到两个 Pod 和一个名为 nginx-service 的服务创建成功了,该 Service 监听的端口是 5000,同时它会把流量转发给它代理的所有 Pod(我们这里就是拥有 app: nginx 标签的两个 Pod)。
我们可以看到两个 Pod 和一个名为 nginx-service 的服务创建成功了,该 Service 监听的端口是 5000,同时它会把流量转发给它代理的所有 Pod(我们这里就是拥有 app: nginx 标签的两个 Pod)。
现在我们再来创建一个普通的 Pod,观察下该 Pod 中的环境变量是否包含上面的 nginx-service 的服务信息:(test-pod.yaml)
现在我们再来创建一个普通的 Pod,观察下该 Pod 中的环境变量是否包含上面的 nginx-service 的服务信息:(test-pod.yaml)
```yaml
apiVersion: v1
kind: Pod
......@@ -81,7 +81,7 @@ $ kubectl create -f test-pod.yaml
pod "test-pod" created
```
等 Pod 创建完成后,我们查看日志信息:
等 Pod 创建完成后,我们查看日志信息:
```shell
$ kubectl logs test-pod
...
......@@ -107,14 +107,14 @@ PWD=/
...
```
我们可以看到打印了很多环境变量处理,其中就包括我们刚刚创建的 nginx-service 这个服务,有 HOST、PORT、PROTO、ADDR 等,也包括其他已经存在的 Service 的环境变量,现在如果我们需要在这个 Pod 里面访问 nginx-service 的服务,我们是不是可以直接通过 NGINX_SERVICE_SERVICE_HOST 和 NGINX_SERVICE_SERVICE_PORT 就可以了,但是我们也知道如果这个 Pod 启动起来的时候如果 nginx-service 服务还没启动起来,在环境变量中我们是无法获取到这些信息的,当然我们可以通过 initContainer 之类的方法来确保 nginx-service 启动后再启动 Pod,但是这种方法毕竟增加了 Pod 启动的复杂性,所以这不是最优的方法。
我们可以看到打印了很多环境变量处理,其中就包括我们刚刚创建的 nginx-service 这个服务,有 HOST、PORT、PROTO、ADDR 等,也包括其他已经存在的 Service 的环境变量,现在如果我们需要在这个 Pod 里面访问 nginx-service 的服务,我们是不是可以直接通过 NGINX_SERVICE_SERVICE_HOST 和 NGINX_SERVICE_SERVICE_PORT 就可以了,但是我们也知道如果这个 Pod 启动起来的时候如果 nginx-service 服务还没启动起来,在环境变量中我们是无法获取到这些信息的,当然我们可以通过 initContainer 之类的方法来确保 nginx-service 启动后再启动 Pod,但是这种方法毕竟增加了 Pod 启动的复杂性,所以这不是最优的方法。
## KubeDNS
由于上面环境变量这种方式的局限性,我们需要一种更加智能的方案,其实我们可以自己想学一种比较理想的方案:那就是可以直接使用 Service 的名称,因为 Service 的名称不会变化,我们不需要去关心分配的 ClusterIP 的地址,因为这个地址并不是固定不变的,所以如果我们直接使用 Service 的名字,然后对应的 ClusterIP 地址的转换能够自动完成就很好了。我们知道名字和 IP 直接的转换是不是和我们平时访问的网站非常类似啊?他们之间的转换功能通过 DNS 就可以解决了,同样的,Kubernetes 也提供了 DNS 的方案来解决上面的服务发现的问题。
### kubedns 介绍
DNS 服务不是一个独立的系统服务,而是作为一种 addon 插件而存在,也就是说不是 Kubernetes 集群必须安装的,当然我们强烈推荐安装,可以将这个插件看成是一种运行在 Kubernetes 集群上的一直比较特殊的应用,现在比较推荐的两个插件:kube-dns 和 CoreDNS。我们在前面使用 kubeadm 搭建集群的时候直接安装的 kube-dns 插件,如果不记得了可以回头去看一看。当然如果我们想使用 CoreDNS 的话也很方便,只需要执行下面的命令即可:
DNS 服务不是一个独立的系统服务,而是作为一种 addon 插件而存在,也就是说不是 Kubernetes 集群必须安装的,当然我们强烈推荐安装,可以将这个插件看成是一种运行在 Kubernetes 集群上的一直比较特殊的应用,现在比较推荐的两个插件:kube-dns 和 CoreDNS。我们在前面使用 kubeadm 搭建集群的时候直接安装的 kube-dns 插件,如果不记得了可以回头去看一看。当然如果我们想使用 CoreDNS 的话也很方便,只需要执行下面的命令即可:
```shell
$ kubeadm init --feature-gates=CoreDNS=true
```
......@@ -135,7 +135,7 @@ $ kubectl describe pod kube-dns-5868f69869-zp5kz -n kube-system
kube-dns、dnsmasq-nanny、sidecar 这3个容器分别实现了什么功能?
* kubedns: kubedns 基于 SkyDNS 库,通过 apiserver 监听 Service 和 Endpoints 的变更事件同时也同步到本地 Cache,实现了一个实时的 Kubernetes 集群内 Service 和 Pod 的 DNS服务发现
* kubedns: kubedns 基于 SkyDNS 库,通过 apiserver 监听 Service 和 Endpoints 的变更事件同时也同步到本地 Cache,实现了一个实时的 Kubernetes 集群内 Service 和 Pod 的 DNS服务发现
* dnsmasq: dsnmasq 容器则实现了 DNS 的缓存功能(在内存中预留一块默认大小为 1G 的地方,保存当前最常用的 DNS 查询记录,如果缓存中没有要查找的记录,它会到 kubedns 中查询,并把结果缓存起来),通过监听 ConfigMap 来动态生成配置
* sider: sidecar 容器实现了可配置的 DNS 探测,并采集对应的监控指标暴露出来供 prometheus 使用
......@@ -145,7 +145,7 @@ kube-dns、dnsmasq-nanny、sidecar 这3个容器分别实现了什么功能?
DNS Pod 具有静态 IP 并作为 Kubernetes 服务暴露出来。该静态 IP 被分配后,kubelet 会将使用 `--cluster-dns = <dns-service-ip>`参数配置的 DNS 传递给每个容器。DNS 名称也需要域名,本地域可以使用参数`--cluster-domain = <default-local-domain>`在 kubelet 中配置。
我们说 dnsmasq 容器通过监听 ConfigMap 来动态生成配置,可以自定义存根域和上下游域名服务器。
我们说 dnsmasq 容器通过监听 ConfigMap 来动态生成配置,可以自定义存根域和上下游域名服务器。
例如,下面的 ConfigMap 建立了一个 DNS 配置,它具有一个单独的存根域和两个上游域名服务器:
```yaml
......@@ -189,9 +189,9 @@ data:
我们前面说了如果我们建立的 Service 如果支持域名形式进行解析,就可以解决我们的服务发现的功能,那么利用 kubedns 可以将 Service 生成怎样的 DNS 记录呢?
* 普通的 Service:会生成 servicename.namespace.svc.cluster.local 的域名,会解析到 Service 对应的 ClusterIP 上,在 Pod 之间的调用可以简写成 servicename.namespace,如果处于同一个命名空间下面,甚至可以只写成 servicename 即可访问
* Headless Service:无头服务,就是把 clusterIP 设置为 None 的,会被解析为指定 Pod 的 IP 列表,同样还可以通过 podname.servicename.namespace.svc.cluster.local 访问到具体的某一个 Pod。
* Headless Service:无头服务,就是把 clusterIP 设置为 None 的,会被解析为指定 Pod 的 IP 列表,同样还可以通过 podname.servicename.namespace.svc.cluster.local 访问到具体的某一个 Pod。
> CoreDNS 实现的功能和 KubeDNS 是一致的,不过 CoreDNS 的所有功能都集成在了同一个容器中,在最新版的1.11.0版本中官方已经推荐使用 CoreDNS了,大家也可以安装 CoreDNS 来代替 KubeDNS,其他使用方法都是一致的:https://coredns.io/
> CoreDNS 实现的功能和 KubeDNS 是一致的,不过 CoreDNS 的所有功能都集成在了同一个容器中,在最新版的1.11.0版本中官方已经推荐使用 CoreDNS了,大家也可以安装 CoreDNS 来代替 KubeDNS,其他使用方法都是一致的:https://coredns.io/
## 测试
现在我们来使用一个简单 Pod 来测试下 Service 的域名访问:
......@@ -205,20 +205,20 @@ options ndots:5
/ #
```
我们进入到 Pod 中,查看**/etc/resolv.conf**中的内容,可以看到 nameserver 的地址**10.96.0.10**,该 IP 地址即是在安装 kubedns 插件的时候集群分配的一个固定的静态 IP 地址,我们可以通过下面的命令进行查看:
我们进入到 Pod 中,查看**/etc/resolv.conf**中的内容,可以看到 nameserver 的地址**10.96.0.10**,该 IP 地址即是在安装 kubedns 插件的时候集群分配的一个固定的静态 IP 地址,我们可以通过下面的命令进行查看:
```shell
$ kubectl get svc kube-dns -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP 62d
```
也就是说我们这个 Pod 现在默认的 nameserver 就是 kubedns 的地址,现在我们来访问下前面我们创建的 nginx-service 服务:
也就是说我们这个 Pod 现在默认的 nameserver 就是 kubedns 的地址,现在我们来访问下前面我们创建的 nginx-service 服务:
```shell
/ # wget -q -O- nginx-service.default.svc.cluster.local
```
可以看到上面我们使用 wget 命令去访问 nginx-service 服务的域名的时候被 hang 住了,没有得到期望的结果,这是因为上面我们建立 Service 的时候暴露的端口是 5000:
可以看到上面我们使用 wget 命令去访问 nginx-service 服务的域名的时候被 hang 住了,没有得到期望的结果,这是因为上面我们建立 Service 的时候暴露的端口是 5000:
```shell
/ # wget -q -O- nginx-service.default.svc.cluster.local:5000
<!DOCTYPE html>
......@@ -248,6 +248,6 @@ Commercial support is available at
</html>
```
加上 5000 端口,就正常访问到服务,再试一试访问:nginx-service.default.svc、nginx-service.default、nginx-service,不出意外这些域名都可以正常访问到期望的结果。
加上 5000 端口,就正常访问到服务,再试一试访问:nginx-service.default.svc、nginx-service.default、nginx-service,不出意外这些域名都可以正常访问到期望的结果。
到这里我们是不是就实现了在集群内部通过 Service 的域名形式进行互相通信了,大家下去试着看看访问不同 namespace 下面的服务呢?下节课我们来给大家讲解使用 ingress 来实现集群外部的服务发现功能。
到这里我们是不是就实现了在集群内部通过 Service 的域名形式进行互相通信了,大家下去试着看看访问不同 namespace 下面的服务呢?下节课我们来给大家讲解使用 ingress 来实现集群外部的服务发现功能。
......@@ -143,9 +143,9 @@ nodeSelector:
```
由于我们这里的特殊性,只有 master 节点有外网访问权限,所以我们使用`nodeSelector`标签将`traefik`的固定调度到`master`这个节点上,那么上面的**tolerations**是干什么的呢?这个是因为我们集群使用的 kubeadm 安装的,master 节点默认是不能被普通应用调度的,要被调度的话就需要添加这里的 tolerations 属性,当然如果你的集群和我们的不太一样,直接去掉这里的调度策略就行。
> nodeSelector 和 tolerations 都属于 Pod 的调度策略,在后面的课程中会为大家讲解。
> nodeSelector 和 tolerations 都属于 Pod 的调度策略,在后面的课程中会为大家讲解。
traefik 还提供了一个 web ui 工具,就是上面的 8080 端口对应的服务,为了能够访问到该服务,我们这里将服务设置成的 NodePort:
traefik 还提供了一个 web ui 工具,就是上面的 8080 端口对应的服务,为了能够访问到该服务,我们这里将服务设置成的 NodePort:
```shell
$ kubectl get pods -n kube-system -l k8s-app=traefik-ingress-lb -o wide
NAME READY STATUS RESTARTS AGE IP NODE
......@@ -162,7 +162,7 @@ traefik-ingress-service NodePort 10.102.183.112 <none> 80:30539/TC
## Ingress 对象
现在我们是通过 NodePort 来访问 traefik 的 Dashboard 的,那怎样通过 ingress 来访问呢?
现在我们是通过 NodePort 来访问 traefik 的 Dashboard 的,那怎样通过 ingress 来访问呢?
首先,需要创建一个 ingress 对象:(ingress.yaml)
```yaml
apiVersion: extensions/v1beta1
......@@ -187,17 +187,17 @@ spec:
$ kubectl create -f ingress.yaml
ingress.extensions "traefik-web-ui" created
```
要注意上面的 ingress 对象的规则,特别是 rules 区域,我们这里是要为 traefik 的 dashboard 建立一个 ingress 对象,所以这里的 serviceName 对应的是上面我们创建的 traefik-ingress-service,端口也要注意对应 8080 端口,为了避免端口更改,这里的 servicePort 的值也可以替换成上面定义的 port 的名字:**admin**
要注意上面的 ingress 对象的规则,特别是 rules 区域,我们这里是要为 traefik 的 dashboard 建立一个 ingress 对象,所以这里的 serviceName 对应的是上面我们创建的 traefik-ingress-service,端口也要注意对应 8080 端口,为了避免端口更改,这里的 servicePort 的值也可以替换成上面定义的 port 的名字:**admin**
创建完成后,我们应该怎么来测试呢?
创建完成后,我们应该怎么来测试呢?
* 第一步,在本地的**/etc/hosts**里面添加上 traefik.haimaxy.com 与 master 节点外网 IP 的映射关系
* 第二步,在浏览器中访问:http://traefik.haimaxy.com,我们会发现并没有得到我们期望的 dashboard 界面,这是因为我们上面部署 traefik 的时候使用的是 NodePort 这种 Service 对象,所以我们只能通过上面的 30539 端口访问到我们的目标对象:http://traefik.haimaxy.com:30539
![traefik dashboard](./images/ingress-config2.png)
加上端口后我们发现可以访问到 dashboard 了,而且在 dashboard 当中多了一条记录,正是上面我们创建的 ingress 对象的数据,我们还可以切换到 HEALTH 界面中,可以查看当前 traefik 代理的服务的整体的健康状态
加上端口后我们发现可以访问到 dashboard 了,而且在 dashboard 当中多了一条记录,正是上面我们创建的 ingress 对象的数据,我们还可以切换到 HEALTH 界面中,可以查看当前 traefik 代理的服务的整体的健康状态
![traefik health](./images/ingress-config3.png)
* 第三步,上面我们可以通过自定义域名加上端口可以访问我们的服务了,但是我们平时服务别人的服务是不是都是直接用的域名啊,http 或者 https 的,几乎很少有在域名后面加上端口访问的吧?为什么?太麻烦啊,端口也记不住,要解决这个问题,怎么办,我们只需要把我们上面的 traefik 的核心应用的端口隐射到 master 节点上的 80 端口,是不是就可以了,因为 http 默认就是访问 80 端口,但是我们在 Service 里面是添加的一个 NodePort 类型的服务,没办法隐射 80 端口,怎么办?这里就可以直接在 Pod 中指定一个 hostPort 即可,更改上面的 traefik.yaml 文件中的容器端口:
* 第三步,上面我们可以通过自定义域名加上端口可以访问我们的服务了,但是我们平时服务别人的服务是不是都是直接用的域名啊,http 或者 https 的,几乎很少有在域名后面加上端口访问的吧?为什么?太麻烦啊,端口也记不住,要解决这个问题,怎么办,我们只需要把我们上面的 traefik 的核心应用的端口隐射到 master 节点上的 80 端口,是不是就可以了,因为 http 默认就是访问 80 端口,但是我们在 Service 里面是添加的一个 NodePort 类型的服务,没办法隐射 80 端口,怎么办?这里就可以直接在 Pod 中指定一个 hostPort 即可,更改上面的 traefik.yaml 文件中的容器端口:
```yaml
containers:
......@@ -219,8 +219,8 @@ $ kubectl apply -f traefik.yaml
更新完成后,这个时候我们在浏览器中直接使用域名方法测试下:
![traefik dashboard](./images/ingress-config4.png)
* 第四步,正常来说,我们如果有自己的域名,我们可以将我们的域名添加一条 DNS 记录,解析到 master 的外网 IP 上面,这样任何人都可以通过域名来访问我的暴露的服务了。
* 第四步,正常来说,我们如果有自己的域名,我们可以将我们的域名添加一条 DNS 记录,解析到 master 的外网 IP 上面,这样任何人都可以通过域名来访问我的暴露的服务了。
> 如果你有多个边缘节点的话,可以在每个边缘节点上部署一个 ingress-controller 服务,然后在边缘节点前面挂一个负载均衡器,比如 nginx,将所有的边缘节点均作为这个负载均衡器的后端,这样就可以实现 ingress-controller 的高可用和负载均衡了。
到这里我们就通过 ingress 对象对外成功暴露了一个服务,下节课我们再来详细了解 traefik 的更多用法。
到这里我们就通过 ingress 对象对外成功暴露了一个服务,下节课我们再来详细了解 traefik 的更多用法。
......@@ -30,7 +30,7 @@ defaultEntryPoints = ["http", "https"]
KeyFile = "/ssl/tls.key"
```
上面的配置文件中我们配置了 http 和 https 两个入口,并且配置了将 http 服务强制跳转到 https 服务,这样我们所有通过 traefik 进来的服务都是 https 的,要访问 https 服务,当然就得配置对应的证书了,可以看到我们指定了 CertFile 和 KeyFile 两个文件,由于 traefik pod 中并没有这两个证书,所以我们要想办法将上面生成的证书挂载到 Pod 中去,是不是前面我们讲解过 secret 对象可以通过 volume 形式挂载到 Pod 中?至于上面的 traefik.toml 这个文件我们要怎么让 traefik pod 能够访问到呢?还记得我们前面讲过的 ConfigMap 吗?我们是不是可以将上面的 traefik.toml 配置文件通过一个 ConfigMap 对象挂载到 traefik pod 中去:
上面的配置文件中我们配置了 http 和 https 两个入口,并且配置了将 http 服务强制跳转到 https 服务,这样我们所有通过 traefik 进来的服务都是 https 的,要访问 https 服务,当然就得配置对应的证书了,可以看到我们指定了 CertFile 和 KeyFile 两个文件,由于 traefik pod 中并没有这两个证书,所以我们要想办法将上面生成的证书挂载到 Pod 中去,是不是前面我们讲解过 secret 对象可以通过 volume 形式挂载到 Pod 中?至于上面的 traefik.toml 这个文件我们要怎么让 traefik pod 能够访问到呢?还记得我们前面讲过的 ConfigMap 吗?我们是不是可以将上面的 traefik.toml 配置文件通过一个 ConfigMap 对象挂载到 traefik pod 中去:
```shell
$ kubectl create configmap traefik-conf --from-file=traefik.toml -n kube-system
```
......@@ -92,7 +92,7 @@ spec:
- --logLevel=INFO
```
和之前的比较,我们增加了 443 的端口配置,以及启动参数中通过 configfile 指定了 traefik.toml 配置文件,这个配置文件是通过 volume 挂载进来的。然后更新下 traefik pod:
和之前的比较,我们增加了 443 的端口配置,以及启动参数中通过 configfile 指定了 traefik.toml 配置文件,这个配置文件是通过 volume 挂载进来的。然后更新下 traefik pod:
```shell
$ kubectl apply -f traefik.yaml
$ kubectl logs -f traefik-ingress-controller-7dcfd9c6df-v58k7 -n kube-system
......@@ -101,15 +101,15 @@ time="2018-08-26T11:26:44Z" level=info msg="Server configuration reloaded on :44
time="2018-08-26T11:26:44Z" level=info msg="Server configuration reloaded on :8080"
```
更新完成后我们查看 traefik pod 的日志,如果出现类似于上面的一些日志信息,证明更新成功了。现在我们去访问 traefik 的 dashboard 会跳转到 https 的地址,并会提示证书相关的报警信息,这是因为我们的证书是我们自建的,并不受浏览器信任,如果你是正规机构购买的证书并不会出现改报警信息,你应该可以看到我们常见的绿色标志
更新完成后我们查看 traefik pod 的日志,如果出现类似于上面的一些日志信息,证明更新成功了。现在我们去访问 traefik 的 dashboard 会跳转到 https 的地址,并会提示证书相关的报警信息,这是因为我们的证书是我们自建的,并不受浏览器信任,如果你是正规机构购买的证书并不会出现改报警信息,你应该可以看到我们常见的绿色标志
![traefik tls warning](./images/traefik-tls.png)
点击下面的高级,我们可以强制让其跳转,这样我们就可以正常访问 traefik 的 dashboard 了。
### 配置 ingress
### 配置 ingress
其实上面的 TLS 认证方式已经成功了,接下来我们通过一个实例来说明下 ingress 中 path 的用法,这里我们部署了3个简单的 web 服务,通过一个环境变量来标识当前运行的是哪个服务:(backend.yaml)
其实上面的 TLS 认证方式已经成功了,接下来我们通过一个实例来说明下 ingress 中 path 的用法,这里我们部署了3个简单的 web 服务,通过一个环境变量来标识当前运行的是哪个服务:(backend.yaml)
```yaml
kind: Deployment
......@@ -288,15 +288,15 @@ Events: <none>
现在我们可以在本地 hosts 里面给域名 example.haimaxy.com 添加对应的 hosts 解析,然后就可以在浏览器中访问,可以看到默认也会跳转到 https 的页面:
![demo1](./images/traefik-tls-demo1.png)
我们可以看到访问上面的域名得到的结果是 svc3 service!这是因为上面在 ingress 中我们为域名的跟路径匹配的是 svc3 这个 service,同样的,我们访问http://example.haimaxy.com/s1 得到的应该就是 svc1 这个 service 了:
我们可以看到访问上面的域名得到的结果是 svc3 service!这是因为上面在 ingress 中我们为域名的跟路径匹配的是 svc3 这个 service,同样的,我们访问http://example.haimaxy.com/s1 得到的应该就是 svc1 这个 service 了:
![demo2](./images/traefik-tls-demo2.png)
访问http://example.haimaxy.com/s2 得到的应该就是 svc2 这个 service 了:
访问http://example.haimaxy.com/s2 得到的应该就是 svc2 这个 service 了:
![demo3](./images/traefik-tls-demo3.png)
> 这里我们需要注意的是根路径`/`必须在 ingress 对象中声明的时候必须放在最后面,不然就都被`/`匹配到拦截到了,大家可以尝试下把`/`这个 path 放在最上面,然后访问下 s1 和 s2 这两个 path,看看得到的结果是怎样的?
有的同学可能有这样的需求,就是不同的 ingress 对象是供不同的域名进行使用的,然后不同的域名的证书还不相同,这样我们想使用上面 traefik 给大家提供的统一的 https 证书就不行了,这个时候我们就可以单独为当前的服务提供单独的证书就可以,同样用证书文件创建一个 secret 对象,然后在 ingress 对象中声明一个 tls 对象即可,比如上面的 example.haimaxy.com 我们可以单独指定一个证书文件:
有的同学可能有这样的需求,就是不同的 ingress 对象是供不同的域名进行使用的,然后不同的域名的证书还不相同,这样我们想使用上面 traefik 给大家提供的统一的 https 证书就不行了,这个时候我们就可以单独为当前的服务提供单独的证书就可以,同样用证书文件创建一个 secret 对象,然后在 ingress 对象中声明一个 tls 对象即可,比如上面的 example.haimaxy.com 我们可以单独指定一个证书文件:
```yaml
apiVersion: extensions/v1beta1
kind: Ingress
......
......@@ -64,7 +64,7 @@ NAME CHART VERSION APP VERSION DESCRIPTION
...
stable/mysql 0.10.1 5.7.14 Fast, reliable, scalable, and easy to use open-source rel...
stable/mysqldump 0.1.0 5.7.21 A Helm chart to help backup MySQL databases using mysqldump
stable/prometheus-mysql-exporter 0.1.0 v0.10.0 A Helm chart for prometheus
stable/prometheus-mysql-exporter 0.1.0 v0.10.0 A Helm chart for prometheus
stable/mariadb 4.4.0 10.1.35 Fast, reliable, scalable, and easy to use open-source rel...
...
```
......@@ -416,7 +416,7 @@ mewing-squid-mysql ClusterIP 10.108.197.48 <none> 3306/TCP
mydb-mysql NodePort 10.96.150.198 <none> 3306:32604/TCP 8m
```
看到服务 mydb-mysql 变成了 NodePort 类型的,二之前默认创建的 mewing-squid-mysql 是 ClusterIP 类型的,证明上面我们通过 YAML 文件来覆盖 values 是成功的。
看到服务 mydb-mysql 变成了 NodePort 类型的,二之前默认创建的 mewing-squid-mysql 是 ClusterIP 类型的,证明上面我们通过 YAML 文件来覆盖 values 是成功的。
接下来我们查看下 Pod 的状况:
```shell
......@@ -426,7 +426,7 @@ mewing-squid-mysql-69f587bdf9-z7glv 0/1 Pending 0 49m
mydb-mysql-dfc999888-hbw5d 0/1 Pending 0 11m
```
比较奇怪的是之前默认创建的和现在的 mydb 的 release 创建的 Pod 都是 Pending 状态,直接使用 describe 命令查看下:
比较奇怪的是之前默认创建的和现在的 mydb 的 release 创建的 Pod 都是 Pending 状态,直接使用 describe 命令查看下:
```shell
$ kubectl describe pod mydb-mysql-dfc999888-hbw5d
Name: mydb-mysql-dfc999888-hbw5d
......@@ -443,7 +443,7 @@ Events:
我们可以发现两个 Pod 处于 Pending 状态的原因都是 PVC 没有被绑定上,所以这里我们可以通过 storageclass 或者手动创建一个合适的 PV 对象来解决这个问题。
另外为了说明 helm 更新的用法,我们这里来直接禁用掉数据持久化,可以在上面的 config.yaml 文件中设置:
另外为了说明 helm 更新的用法,我们这里来直接禁用掉数据持久化,可以在上面的 config.yaml 文件中设置:
```yaml
persistence:
enabled: false
......@@ -485,7 +485,7 @@ mydb-mysql-6ffc84bbf6-lcn4d 0/1 PodInitializing 0
...
```
我们看到 mydb 关联的 Pod 已经变成了 PodInitializing 的状态,已经不是 Pending 状态了,同样的,使用 describe 命令查看:
我们看到 mydb 关联的 Pod 已经变成了 PodInitializing 的状态,已经不是 Pending 状态了,同样的,使用 describe 命令查看:
```shell
$ kubectl describe pod mydb-mysql-6ffc84bbf6-lcn4d
Name: mydb-mysql-6ffc84bbf6-lcn4d
......@@ -510,7 +510,7 @@ Events:
Normal Pulling 41s kubelet, node02 pulling image "mysql:5.7.14"
```
我们可以看到现在没有任何关于 PVC 的错误信息了,这是因为我们刚刚更新的版本中就是禁用掉了的数据持久化的,证明 helm upgrade 和 --values 是生效了的。现在我们使用 helm ls 命令查看先当前的 release:
我们可以看到现在没有任何关于 PVC 的错误信息了,这是因为我们刚刚更新的版本中就是禁用掉了的数据持久化的,证明 helm upgrade 和 --values 是生效了的。现在我们使用 helm ls 命令查看先当前的 release:
```shell
$ helm ls
NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
......@@ -526,7 +526,7 @@ REVISION UPDATED STATUS CHART DESCRIPTION
2 Wed Sep 5 00:38:33 2018 DEPLOYED mysql-0.10.1 Upgrade complete
```
当然如果我们要回滚到某一个版本的话,使用 helm rollback 命令即可,比如我们将 mydb 回滚到上一个版本:
当然如果我们要回滚到某一个版本的话,使用 helm rollback 命令即可,比如我们将 mydb 回滚到上一个版本:
```shell
$ $ helm rollback mydb 1
```
......@@ -551,7 +551,7 @@ mydb 2 Wed Sep 5 00:38:33 2018 DEPLOYED mysql-0.10.1 5.7.14
`helm list --all`则会显示所有的 release,包括已经被删除的
> 由于 Helm 保留已删除 release 的记录,因此不能重新使用 release 名称。(如果 确实 需要重新使用此 release 名称,则可以使用此 --replace 参数,但它只会重用现有 release 并替换其资源。)这点是不是和 docker container 的管理比较类似
> 由于 Helm 保留已删除 release 的记录,因此不能重新使用 release 名称。(如果 确实 需要重新使用此 release 名称,则可以使用此 --replace 参数,但它只会重用现有 release 并替换其资源。)这点是不是和 docker container 的管理比较类似
请注意,因为 release 以这种方式保存,所以可以回滚已删除的资源并重新激活它。
......@@ -561,4 +561,4 @@ $ helm delete mewing-squid --purge
release "mewing-squid" deleted
```
我们这里只是讲到了 Helm 的一些常用的方法,更多用法我们将在后面的课程中和大家接触到。
我们这里只是讲到了 Helm 的一些常用的方法,更多用法我们将在后面的课程中和大家接触到。
......@@ -7,7 +7,7 @@
`kube-scheduler` 是 kubernetes 的调度器,它的主要作用就是根据特定的调度算法和调度策略将 Pod 调度到合适的 Node 节点上去,是一个独立的二进制程序,启动之后会一直监听 API Server,获取到 PodSpec.NodeName 为空的 Pod,对每个 Pod 都会创建一个 binding。
![kube-scheduler structrue](./images/kube-scheduler-structrue.jpg)
这个过程在我们看来好像比较简单,但在实际的生产环境中,需要考虑的问题就有很多了:
* 如何保证全部的节点调度的公平性?要知道并不是说有节点资源配置都是一样的
......@@ -21,13 +21,13 @@
kubernetes 调度器的源码位于 kubernetes/pkg/scheduler 中,大体的代码目录结构如下所示:(不同的版本目录结构可能不太一样)
```shell
kubernetes/pkg/scheduler
-- scheduler.go //调度相关的具体实现
kubernetes/pkg/scheduler
-- scheduler.go //调度相关的具体实现
|-- algorithm
| |-- predicates //节点筛选策略
| |-- priorities //节点打分策略
|-- algorithmprovider
| |-- defaults //定义默认的调度器
| |-- defaults //定义默认的调度器
```
其中 Scheduler 创建和运行的核心程序,对应的代码在 pkg/scheduler/scheduler.go,如果要查看`kube-scheduler`的入口程序,对应的代码在 cmd/kube-scheduler/scheduler.go。
......@@ -38,11 +38,11 @@ kubernetes/pkg/scheduler
* 然后是**优选过程**,对通过的节点按照优先级排序,称之为`Priorities`
* 最后从中选择优先级最高的节点,如果中间任何一步骤有错误,就直接返回错误
`Predicates`阶段首先遍历全部节点,过滤掉不满足条件的节点,属于强制性规则,这一阶段输出的所有满足要求的 Node 将被记录并作为第二阶段的输入,如果所有的节点都不满足条件,那么 Pod 将会一直处于 Pending 状态,直到有节点满足条件,在这期间调度器会不断的重试。
`Predicates`阶段首先遍历全部节点,过滤掉不满足条件的节点,属于强制性规则,这一阶段输出的所有满足要求的 Node 将被记录并作为第二阶段的输入,如果所有的节点都不满足条件,那么 Pod 将会一直处于 Pending 状态,直到有节点满足条件,在这期间调度器会不断的重试。
> 所以我们在部署应用的时候,如果发现有 Pod 一直处于 Pending 状态,那么就是没有满足调度条件的节点,这个时候可以去检查下节点资源是否可用。
`Priorities`阶段即再次对节点进行筛选,如果有多个节点都满足条件的话,那么系统会按照节点的优先级(priorites)大小对节点进行排序,最后选择优先级最高的节点来部署 Pod 应用。
`Priorities`阶段即再次对节点进行筛选,如果有多个节点都满足条件的话,那么系统会按照节点的优先级(priorites)大小对节点进行排序,最后选择优先级最高的节点来部署 Pod 应用。
下面是调度过程的简单示意图:
![kube-scheduler filter](./images/kube-scheduler-filter.jpg)
......@@ -52,31 +52,31 @@ kubernetes/pkg/scheduler
* 首先,客户端通过 API Server 的 REST API 或者 kubectl 工具创建 Pod 资源
* API Server 收到用户请求后,存储相关数据到 etcd 数据库中
* 调度器监听 API Server 查看为调度(bind)的 Pod 列表,循环遍历地为每个 Pod 尝试分配节点,这个分配过程就是我们上面提到的两个阶段:
* 调度器监听 API Server 查看为调度(bind)的 Pod 列表,循环遍历地为每个 Pod 尝试分配节点,这个分配过程就是我们上面提到的两个阶段:
* 预选阶段(Predicates),过滤节点,调度器用一组规则过滤掉不符合要求的 Node 节点,比如 Pod 设置了资源的 request,那么可用资源比 Pod 需要的资源少的主机显然就会被过滤掉
* 优选阶段(Priorities),为节点的优先级打分,将上一阶段过滤出来的 Node 列表进行打分,调度器会考虑一些整体的优化策略,比如把 Deployment 控制的多个 Pod 副本分布到不同的主机上,使用最低负载的主机等等策略
* 经过上面的阶段过滤后选择打分最高的 Node 节点和 Pod 进行 binding 操作,然后将结果存储到 etcd 中
* 最后被选择出来的 Node 节点对应的 kubelet 去执行创建 Pod 的相关操作
* 最后被选择出来的 Node 节点对应的 kubelet 去执行创建 Pod 的相关操作
其中`Predicates`过滤有一系列的算法可以使用,我们这里简单列举几个:
其中`Predicates`过滤有一系列的算法可以使用,我们这里简单列举几个:
* PodFitsResources:节点上剩余的资源是否大于 Pod 请求的资源
* PodFitsHost:如果 Pod 指定了 NodeName,检查节点名称是否和 NodeName 匹配
* PodFitsHostPorts:节点上已经使用的 port 是否和 Pod 申请的 port 冲突
* PodSelectorMatches:过滤掉和 Pod 指定的 label 不匹配的节点
* NoDiskConflict:已经 mount 的 volume 和 Pod 指定的 volume 不冲突,除非它们都是只读的
* CheckNodeDiskPressure:检查节点磁盘空间是否符合要求
* NoDiskConflict:已经 mount 的 volume 和 Pod 指定的 volume 不冲突,除非它们都是只读的
* CheckNodeDiskPressure:检查节点磁盘空间是否符合要求
* CheckNodeMemoryPressure:检查节点内存是否够用
除了这些过滤算法之外,还有一些其他的算法,更多更详细的我们可以查看源码文件:k8s.io/kubernetes/pkg/scheduler/algorithm/predicates/predicates.go。
除了这些过滤算法之外,还有一些其他的算法,更多更详细的我们可以查看源码文件:k8s.io/kubernetes/pkg/scheduler/algorithm/predicates/predicates.go。
`Priorities`优先级是由一系列键值对组成的,键是该优先级的名称,值是它的权重值,同样,我们这里给大家列举几个具有代表性的选项:
`Priorities`优先级是由一系列键值对组成的,键是该优先级的名称,值是它的权重值,同样,我们这里给大家列举几个具有代表性的选项:
* LeastRequestedPriority:通过计算 CPU 和内存的使用率来决定权重,使用率越低权重越高,当然正常肯定也是资源是使用率越低权重越高,能给别的 Pod 运行的可能性就越大
* SelectorSpreadPriority:为了更好的高可用,对同属于一个 Deployment 或者 RC 下面的多个 Pod 副本,尽量调度到多个不同的节点上,当一个 Pod 被调度的时候,会先去查找该 Pod 对应的 controller,然后查看该 controller 下面的已存在的 Pod,运行 Pod 越少的节点权重越高
* SelectorSpreadPriority:为了更好的高可用,对同属于一个 Deployment 或者 RC 下面的多个 Pod 副本,尽量调度到多个不同的节点上,当一个 Pod 被调度的时候,会先去查找该 Pod 对应的 controller,然后查看该 controller 下面的已存在的 Pod,运行 Pod 越少的节点权重越高
* ImageLocalityPriority:就是如果在某个节点上已经有要使用的镜像节点了,镜像总大小值越大,权重就越高
* NodeAffinityPriority:这个就是根据节点的亲和性来计算一个权重值,后面我们会详细讲解亲和性的使用方法
* NodeAffinityPriority:这个就是根据节点的亲和性来计算一个权重值,后面我们会详细讲解亲和性的使用方法
除了这些策略之外,还有很多其他的策略,同样我们可以查看源码文件:k8s.io/kubernetes/pkg/scheduler/algorithm/priorities/ 了解更多信息。每一个优先级函数会返回一个0-10的分数,分数越高表示节点越优,同时每一个函数也会对应一个表示权重的值。最终主机的得分用以下公式计算得出:
```shell
......@@ -88,7 +88,7 @@ finalScoreNode = (weight1 * priorityFunc1) + (weight2 * priorityFunc2) + … + (
上面就是 kube-scheduler 默认调度的基本流程,除了使用默认的调度器之外,我们也可以自定义调度策略。
### 调度器扩展
`kube-scheduler`在启动的时候可以通过 `--policy-config-file`参数来指定调度策略文件,我们可以根据我们自己的需要来组装`Predicates``Priority`函数。选择不同的过滤函数和优先级函数、控制优先级函数的权重、调整过滤函数的顺序都会影响调度过程。
`kube-scheduler`在启动的时候可以通过 `--policy-config-file`参数来指定调度策略文件,我们可以根据我们自己的需要来组装`Predicates``Priority`函数。选择不同的过滤函数和优先级函数、控制优先级函数的权重、调整过滤函数的顺序都会影响调度过程。
下面是官方的 Policy 文件示例:
```json
......@@ -130,7 +130,7 @@ spec:
要开发我们自己的调度器也是比较容易的,比如我们这里的 my-scheduler:
* 首先需要通过指定的 API 获取节点和 Pod
* 首先需要通过指定的 API 获取节点和 Pod
* 然后选择`phase=Pending``schedulerName=my-scheduler`的pod
* 计算每个 Pod 需要放置的位置之后,调度程序将创建一个`Binding`对象
* 然后根据我们自定义的调度器的算法计算出最适合的目标节点
......
......@@ -7,10 +7,10 @@
## 简介
Prometheus 最初是 SoundCloud 构建的开源系统监控和报警工具,是一个独立的开源项目,于2016年加入了 CNCF 基金会,作为继 Kubernetes 之后的第二个托管项目。
Prometheus 最初是 SoundCloud 构建的开源系统监控和报警工具,是一个独立的开源项目,于2016年加入了 CNCF 基金会,作为继 Kubernetes 之后的第二个托管项目。
### 特征
Prometheus 相比于其他传统监控工具主要有以下几个特点:
Prometheus 相比于其他传统监控工具主要有以下几个特点:
* 具有由 metric 名称和键/值对标识的时间序列数据的多维数据模型
* 有一个灵活的查询语言
......@@ -32,15 +32,15 @@ Prometheus 由多个组件组成,但是其中许多组件是可选的:
大多数 Prometheus 组件都是用 Go 编写的,因此很容易构建和部署为静态的二进制文件。
### 架构
下图是 Prometheus 官方提供的架构及其一些相关的生态系统组件:
下图是 Prometheus 官方提供的架构及其一些相关的生态系统组件:
![架构](./images/prometheus-architecture.png)
整体流程比较简单,Prometheus 直接接收或者通过中间的 Pushgateway 网关被动获取指标数据,在本地存储所有的获取的指标数据,并对这些数据进行一些规则整理,用来生成一些聚合数据或者报警信息,Grafana 或者其他工具用来可视化这些数据。
整体流程比较简单,Prometheus 直接接收或者通过中间的 Pushgateway 网关被动获取指标数据,在本地存储所有的获取的指标数据,并对这些数据进行一些规则整理,用来生成一些聚合数据或者报警信息,Grafana 或者其他工具用来可视化这些数据。
## 安装
由于 Prometheus 是 Golang 编写的程序,所以要安装的话也非常简单,只需要将二进制文件下载下来直接执行即可,前往地址:[https://prometheus.io/download](https://prometheus.io/download) 下载我们对应的版本即可。
由于 Prometheus 是 Golang 编写的程序,所以要安装的话也非常简单,只需要将二进制文件下载下来直接执行即可,前往地址:[https://prometheus.io/download](https://prometheus.io/download) 下载我们对应的版本即可。
Prometheus 是通过一个 YAML 配置文件来进行启动的,如果我们使用二进制的方式来启动的话,可以使用下面的命令:
```shell
......@@ -73,7 +73,7 @@ rule_files 模块制定了规则所在的位置,prometheus 可以根据这个
scrape_configs 用于控制 prometheus 监控哪些资源。由于 prometheus 通过 HTTP 的方式来暴露的它本身的监控数据,prometheus 也能够监控本身的健康情况。在默认的配置里有一个单独的 job,叫做prometheus,它采集 prometheus 服务本身的时间序列数据。这个 job 包含了一个单独的、静态配置的目标:监听 localhost 上的9090端口。prometheus 默认会通过目标的`/metrics`路径采集 metrics。所以,默认的 job 通过 URL:`http://localhost:9090/metrics`采集 metrics。收集到的时间序列包含 prometheus 服务本身的状态和性能。如果我们还有其他的资源需要监控的话,直接配置在该模块下面就可以了。
由于我们这里是要跑在 Kubernetes 系统中,所以我们直接用 Docker 镜像的方式运行即可。
由于我们这里是要跑在 Kubernetes 系统中,所以我们直接用 Docker 镜像的方式运行即可。
> 为了方便管理,我们将所有的资源对象都安装在`kube-ops`的 namespace 下面,没有的话需要提前安装。
......@@ -101,7 +101,7 @@ $ kubectl create -f prometheus-cm.yaml
configmap "prometheus-config" created
```
配置文件创建完成了,以后如果我们有新的资源需要被监控,我们只需要将上面的 ConfigMap 对象更新即可。现在我们来创建 prometheus 的 Pod 资源:(prometheus-deploy.yaml)
配置文件创建完成了,以后如果我们有新的资源需要被监控,我们只需要将上面的 ConfigMap 对象更新即可。现在我们来创建 prometheus 的 Pod 资源:(prometheus-deploy.yaml)
```yaml
apiVersion: extensions/v1beta1
kind: Deployment
......@@ -156,7 +156,7 @@ spec:
name: config-volume
```
我们在启动程序的时候,除了指定了 prometheus.yml 文件之外,还通过参数`storage.tsdb.path`指定了 TSDB 数据的存储路径、通过`storage.tsdb.retention`设置了保留多长时间的数据,还有下面的`web.enable-admin-api`参数可以用来开启对 admin api 的访问权限,参数`web.enable-lifecycle`非常重要,用来开启支持热更新的,有了这个参数之后,prometheus.yml 配置文件只要更新了,通过执行`localhost:9090/-/reload`就会立即生效,所以一定要加上这个参数。
我们在启动程序的时候,除了指定了 prometheus.yml 文件之外,还通过参数`storage.tsdb.path`指定了 TSDB 数据的存储路径、通过`storage.tsdb.retention`设置了保留多长时间的数据,还有下面的`web.enable-admin-api`参数可以用来开启对 admin api 的访问权限,参数`web.enable-lifecycle`非常重要,用来开启支持热更新的,有了这个参数之后,prometheus.yml 配置文件只要更新了,通过执行`localhost:9090/-/reload`就会立即生效,所以一定要加上这个参数。
我们这里将 prometheus.yml 文件对应的 ConfigMap 对象通过 volume 的形式挂载进了 Pod,这样 ConfigMap 更新后,对应的 Pod 里面的文件也会热更新的,然后我们再执行上面的 reload 请求,Prometheus 配置就生效了,除此之外,为了将时间序列数据进行持久化,我们将数据目录和一个 pvc 对象进行了绑定,所以我们需要提前创建好这个 pvc 对象:(prometheus-volume.yaml)
```yaml
......@@ -292,15 +292,15 @@ prometheus NodePort 10.111.118.104 <none> 9090:30987/TCP
![prometheus webui](./images/prometheus-webui.png)
为了数据的一致性,prometheus 所有的数据都是使用的 UTC 时间,所以我们默认打开的 dashboard 中有这样一个警告,我们需要在查询的时候指定我们当前的时间才可以。然后我们可以查看当前监控系统中的一些监控目标:
为了数据的一致性,prometheus 所有的数据都是使用的 UTC 时间,所以我们默认打开的 dashboard 中有这样一个警告,我们需要在查询的时候指定我们当前的时间才可以。然后我们可以查看当前监控系统中的一些监控目标:
![prometheus targets](./images/prometheus-menu.png)
由于我们现在还没有配置任何的报警信息,所以 Alerts 菜单下面现在没有任何数据,隔一会儿,我们可以去 Graph 菜单下面查看我们抓取的 prometheus 本身的一些监控数据了,其中`- insert metrics at cursor -`下面就是我们搜集到的一些监控数据指标:
![prometheus metrics](./images/prometheus-metrics-menu.png)
比如我们这里就选择`scrape_duration_seconds`这个指标,然后点击`Execute`,如果这个时候没有查询到任何数据,我们可以切换到`Graph`这个 tab 下面重新选择下时间,选择到当前的时间点,重新执行,就可以看到类似于下面的图表数据了:
比如我们这里就选择`scrape_duration_seconds`这个指标,然后点击`Execute`,如果这个时候没有查询到任何数据,我们可以切换到`Graph`这个 tab 下面重新选择下时间,选择到当前的时间点,重新执行,就可以看到类似于下面的图表数据了:
![prometheus graph](./images/prometheus-metrics-graph.png)
除了简单的直接使用采集到的一些监控指标数据之外,这个时候也可以使用强大的 PromQL 工具,PromQL其实就是 prometheus 便于数据聚合展示开发的一套 ad hoc 查询语言的,你想要查什么找对应函数取你的数据好了。
除了简单的直接使用采集到的一些监控指标数据之外,这个时候也可以使用强大的 PromQL 工具,PromQL其实就是 prometheus 便于数据聚合展示开发的一套 ad hoc 查询语言的,你想要查什么找对应函数取你的数据好了。
下节课我们再来和大家学习怎样使用 Prometheus 来监控 Kubernetes 系统的组件以及常用的资源对象的监控。
下节课我们再来和大家学习怎样使用 Prometheus 来监控 Kubernetes 系统的组件以及常用的资源对象的监控。
# 53. 监控 Kubernetes 集群应用
# 53. 监控 Kubernetes 集群应用
上一节我们和大家介绍了`Prometheus`的数据指标是通过一个公开的 HTTP(S) 数据接口获取到的,我们不需要单独安装监控的 agent,只需要暴露一个 metrics 接口,Prometheus 就会定期去拉取数据;对于一些普通的 HTTP 服务,我们完全可以直接重用这个服务,添加一个`/metrics`接口暴露给 Prometheus;而且获取到的指标数据格式是非常易懂的,不需要太高的学习成本。
......@@ -67,7 +67,7 @@ data:
当然,我们这里只是一个很简单的配置,scrape_configs 下面可以支持很多参数,例如:
* basic_auth 和 bearer_token:比如我们提供的`/metrics`接口需要 basic 认证的时候,通过传统的用户名/密码或者在请求的header中添加对应的 token 都可以支持
* basic_auth 和 bearer_token:比如我们提供的`/metrics`接口需要 basic 认证的时候,通过传统的用户名/密码或者在请求的header中添加对应的 token 都可以支持
* kubernetes_sd_configs 或 consul_sd_configs:可以用来自动发现一些应用的监控数据
由于我们这里 Traefik 对应的 servicename 是`traefik-ingress-service`,并且在 kube-system 这个 namespace 下面,所以我们这里的`targets`的路径配置则需要使用`FQDN`的形式:`traefik-ingress-service.kube-system.svc.cluster.local`,当然如果你的 Traefik 和 Prometheus 都部署在同一个命名空间的话,则直接填 `servicename:serviceport`即可。然后我们重新更新这个 ConfigMap 资源对象:
......@@ -91,14 +91,14 @@ $ curl -X POST "http://10.102.74.90:9090/-/reload"
reload 这个 url 是一个 POST 请求,所以这里我们通过 service 的 CLUSTER-IP:PORT 就可以访问到这个重载的接口,这个时候我们再去看 Prometheus 的 Dashboard 中查看采集的目标数据:
![prometheus dashboard targets](./images/prometheus-dashboard-targets.png)
可以看到我们刚刚添加的`traefik`这个任务已经出现了,然后同样的我们可以切换到 Graph 下面去,我们可以找到一些 Traefik 的指标数据,至于这些指标数据代表什么意义,一般情况下,我们可以去查看对应的`/metrics`接口,里面一般情况下都会有对应的注释。
可以看到我们刚刚添加的`traefik`这个任务已经出现了,然后同样的我们可以切换到 Graph 下面去,我们可以找到一些 Traefik 的指标数据,至于这些指标数据代表什么意义,一般情况下,我们可以去查看对应的`/metrics`接口,里面一般情况下都会有对应的注释。
到这里我们就在 Prometheus 上配置了第一个 Kubernetes 应用。
## 使用 exporter 监控应用
上面我们也说过有一些应用可能没有自带`/metrics`接口供 Prometheus 使用,在这种情况下,我们就需要利用 exporter 服务来为 Prometheus 提供指标数据了。Prometheus 官方为许多应用就提供了对应的 exporter 应用,也有许多第三方的实现,我们可以前往官方网站进行查看:[exporters](https://prometheus.io/docs/instrumenting/exporters/)
比如我们这里通过一个[redis-exporter](https://github.com/oliver006/redis_exporter)的服务来监控 redis 服务,对于这类应用,我们一般会以 sidecar 的形式和主应用部署在同一个 Pod 中,比如我们这里来部署一个 redis 应用,并用 redis-exporter 的方式来采集监控数据供 Prometheus 使用,如下资源清单文件:(prome-redis.yaml)
比如我们这里通过一个[redis-exporter](https://github.com/oliver006/redis_exporter)的服务来监控 redis 服务,对于这类应用,我们一般会以 sidecar 的形式和主应用部署在同一个 Pod 中,比如我们这里来部署一个 redis 应用,并用 redis-exporter 的方式来采集监控数据供 Prometheus 使用,如下资源清单文件:(prome-redis.yaml)
```yaml
apiVersion: extensions/v1beta1
kind: Deployment
......@@ -149,7 +149,7 @@ spec:
targetPort: 9121
```
可以看到上面我们在 redis 这个 Pod 中包含了两个容器,一个就是 redis 本身的主应用,另外一个容器就是 redis_exporter。现在直接创建上面的应用
可以看到上面我们在 redis 这个 Pod 中包含了两个容器,一个就是 redis 本身的主应用,另外一个容器就是 redis_exporter。现在直接创建上面的应用:
```shell
$ kubectl create -f prome-redis.yaml
deployment.extensions "redis" created
......@@ -186,14 +186,14 @@ go_gc_duration_seconds_count 0
redis_used_cpu_user_children{addr="redis://localhost:6379",alias=""} 0
```
同样的,现在我们只需要更新 Prometheus 的配置文件:
同样的,现在我们只需要更新 Prometheus 的配置文件:
```yaml
- job_name: 'redis'
static_configs:
- targets: ['redis:9121']
```
> 由于我们这里的 redis 服务和 Prometheus 处于同一个 namespace,所以我们直接使用 servicename 即可。
> 由于我们这里的 redis 服务和 Prometheus 处于同一个 namespace,所以我们直接使用 servicename 即可。
配置文件更新后,重新加载:
```shell
......@@ -206,15 +206,15 @@ $ curl -X POST "http://10.102.74.90:9090/-/reload"
```
这个时候我们再去看 Prometheus 的 Dashboard 中查看采集的目标数据:
![prometheus targets redis](./images/prometheus-targets-redis.png)
![prometheus targets redis](./images/prometheus-targets-redis.png)
可以看到配置的 redis 这个 job 已经生效了。切换到 Graph 下面可以看到很多关于 redis 的指标数据:
![redis metrics](./images/redis-metrics.png)
我们选择任意一个指标,比如`redis_exporter_scrapes_total`,然后点击执行就可以看到对应的数据图表了:
我们选择任意一个指标,比如`redis_exporter_scrapes_total`,然后点击执行就可以看到对应的数据图表了:
![redis scrapes total](./images/redis-graph.png)
> 注意,如果时间有问题,我们需要手动在 Graph 下面调整下时间

> 注意,如果时间有问题,我们需要手动在 Graph 下面调整下时间
除了监控群集中部署的服务之外,我们下节课再和大家学习怎样监视 Kubernetes 群集本身。
# 54. 监控 Kubernetes 集群节点
# 54. 监控 Kubernetes 集群节点
上节课我们和大家学习了怎样用 Promethues 来监控 Kubernetes 集群中的应用,但是对于 Kubernetes 集群本身的监控也是非常重要的,我们需要时时刻刻了解集群的运行状态。
对于集群的监控一般我们需要考虑以下几个方面:
......@@ -12,13 +12,13 @@ Kubernetes 集群的监控方案目前主要有以下集中方案:
* Heapster:Heapster 是一个集群范围的监控和数据聚合工具,以 Pod 的形式运行在集群中。
![heapster](./images/kubernetes_monitoring_heapster.png)
除了 Kubelet/cAdvisor 之外,我们还可以向 Heapster 添加其他指标源数据,比如 kube-state-metrics,我们会在下面和大家讲解的
除了 Kubelet/cAdvisor 之外,我们还可以向 Heapster 添加其他指标源数据,比如 kube-state-metrics,我们会在下面和大家讲解的
> 需要注意的是 Heapster 已经被废弃了,后续版本中会使用 metrics-server 代替。
> 需要注意的是 Heapster 已经被废弃了,后续版本中会使用 metrics-server 代替。
* cAdvisor:[cAdvisor](https://github.com/google/cadvisor)`Google`开源的容器资源监控和性能分析工具,它是专门为容器而生,本身也支持 Docker 容器,在 Kubernetes 中,我们不需要单独去安装,cAdvisor 作为 kubelet 内置的一部分程序可以直接使用。
* Kube-state-metrics:[kube-state-metrics](https://github.com/kubernetes/kube-state-metrics)通过监听 API Server 生成有关资源对象的状态指标,比如 Deployment、Node、Pod,需要注意的是 kube-state-metrics 只是简单提供一个 metrics 数据,并不会存储这些指标数据,所以我们可以使用 Prometheus 来抓取这些数据然后存储。
* metrics-server:metrics-server 也是一个集群范围内的资源数据聚合工具,是 Heapster 的替代品,同样的,metrics-server 也只是显示数据,并不提供数据存储服务。
* cAdvisor:[cAdvisor](https://github.com/google/cadvisor)`Google`开源的容器资源监控和性能分析工具,它是专门为容器而生,本身也支持 Docker 容器,在 Kubernetes 中,我们不需要单独去安装,cAdvisor 作为 kubelet 内置的一部分程序可以直接使用。
* Kube-state-metrics:[kube-state-metrics](https://github.com/kubernetes/kube-state-metrics)通过监听 API Server 生成有关资源对象的状态指标,比如 Deployment、Node、Pod,需要注意的是 kube-state-metrics 只是简单提供一个 metrics 数据,并不会存储这些指标数据,所以我们可以使用 Prometheus 来抓取这些数据然后存储。
* metrics-server:metrics-server 也是一个集群范围内的资源数据聚合工具,是 Heapster 的替代品,同样的,metrics-server 也只是显示数据,并不提供数据存储服务。
不过 kube-state-metrics 和 metrics-server 之间还是有很大不同的,二者的主要区别如下:
......@@ -26,11 +26,11 @@ Kubernetes 集群的监控方案目前主要有以下集中方案:
* metrics-server 主要关注的是[资源度量 API](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/instrumentation/resource-metrics-api.md) 的实现,比如 CPU、文件描述符、内存、请求延时等指标。
## 监控集群节点
现在我们就来开始我们集群的监控工作,首先来监控我们集群的节点,要监控节点其实我们已经有很多非常成熟的方案了,比如 Nagios、zabbix,甚至我们自己来收集数据也可以,我们这里通过 Prometheus 来采集节点的监控指标数据,可以通过[node_exporter](https://github.com/prometheus/node_exporter)来获取,顾名思义,node_exporter 抓哟就是用于采集服务器节点的各种运行指标的,目前 node_exporter 支持几乎所有常见的监控点,比如 conntrack,cpu,diskstats,filesystem,loadavg,meminfo,netstat等,详细的监控点列表可以参考其[Github repo](https://github.com/prometheus/node_exporter)
现在我们就来开始我们集群的监控工作,首先来监控我们集群的节点,要监控节点其实我们已经有很多非常成熟的方案了,比如 Nagios、zabbix,甚至我们自己来收集数据也可以,我们这里通过 Prometheus 来采集节点的监控指标数据,可以通过[node_exporter](https://github.com/prometheus/node_exporter)来获取,顾名思义,node_exporter 抓哟就是用于采集服务器节点的各种运行指标的,目前 node_exporter 支持几乎所有常见的监控点,比如 conntrack,cpu,diskstats,filesystem,loadavg,meminfo,netstat等,详细的监控点列表可以参考其[Github repo](https://github.com/prometheus/node_exporter)
我们可以通过 DaemonSet 控制器来部署该服务,这样每一个节点都会自动运行一个这样的 Pod,如果我们从集群中删除或者添加节点后,也会进行自动扩展。
我们可以通过 DaemonSet 控制器来部署该服务,这样每一个节点都会自动运行一个这样的 Pod,如果我们从集群中删除或者添加节点后,也会进行自动扩展。
在部署 node-exporter 的时候有一些细节需要注意,如下资源清单文件:(prome-node-exporter.yaml)
在部署 node-exporter 的时候有一些细节需要注意,如下资源清单文件:(prome-node-exporter.yaml)
```yaml
apiVersion: extensions/v1beta1
kind: DaemonSet
......@@ -93,9 +93,9 @@ spec:
path: /
```
由于我们要获取到的数据是主机的监控指标数据,而我们的 node-exporter 是运行在容器中的,所以我们在 Pod 中需要配置一些 Pod 的安全策略,这里我们就添加了`hostPID: true``hostIPC: true``hostNetwork: true`3个策略,用来使用主机的 PID namespace、IPC namespace 以及主机网络,这些 namespace 就是用于容器隔离的关键技术,要注意这里的 namespace 和集群中的 namespace 是两个完全不相同的概念。
由于我们要获取到的数据是主机的监控指标数据,而我们的 node-exporter 是运行在容器中的,所以我们在 Pod 中需要配置一些 Pod 的安全策略,这里我们就添加了`hostPID: true``hostIPC: true``hostNetwork: true`3个策略,用来使用主机的 PID namespace、IPC namespace 以及主机网络,这些 namespace 就是用于容器隔离的关键技术,要注意这里的 namespace 和集群中的 namespace 是两个完全不相同的概念。
另外我们还将主机的`/dev``/proc``/sys`这些目录挂载到容器中,这些因为我们采集的很多节点数据都是通过这些文件夹下面的文件来获取到的,比如我们在使用`top`命令可以查看当前`cpu`使用情况,数据就来源于文件`/proc/stat`,使用`free`命令可以查看当前内存使用情况,其数据来源是来自`/proc/meminfo`文件。
另外我们还将主机的`/dev``/proc``/sys`这些目录挂载到容器中,这些因为我们采集的很多节点数据都是通过这些文件夹下面的文件来获取到的,比如我们在使用`top`命令可以查看当前`cpu`使用情况,数据就来源于文件`/proc/stat`,使用`free`命令可以查看当前内存使用情况,其数据来源是来自`/proc/meminfo`文件。
另外由于我们集群使用的是 kubeadm 搭建的,所以如果希望 master 节点也一起被监控,则需要添加响应的容忍,对于污点和容忍还不是很熟悉的同学可以在前面的章节中回顾下。
......@@ -168,14 +168,14 @@ $ curl -X POST "http://10.102.74.90:9090/-/reload"
配置生效后,我们再去 prometheus 的 dashboard 中查看 Targets 是否能够正常抓取数据,访问**任意节点IP:30358**
![prometheus nodes target](./images/prometheus-nodes-target.png)
![prometheus nodes target](./images/prometheus-nodes-target.png)
我们可以看到上面的`kubernetes-nodes`这个 job 任务已经自动发现了我们3个 node 节点,但是在获取数据的时候失败了,出现了类似于下面的错误信息:
```shell
Get http://10.151.30.57:10250/metrics: net/http: HTTP/1.x transport connection broken: malformed HTTP response "\x15\x03\x01\x00\x02\x02"
```
这个是因为 prometheus 去发现 Node 模式的服务的时候,访问的端口默认是**10250**,而现在该端口下面已经没有了`/metrics`指标数据了,现在 kubelet 只读的数据接口统一通过**10255**端口进行暴露了,所以我们应该去替换掉这里的端口,但是我们是要替换成**10255**端口吗?不是的,因为我们是要去配置上面通过`node-exporter`抓取到的节点指标数据,而我们上面是不是指定了`hostNetwork=true`,所以在每个节点上就会绑定一个端口**9100**,所以我们应该将这里的**10250**替换成**9100**,但是应该怎样替换呢?
这个是因为 prometheus 去发现 Node 模式的服务的时候,访问的端口默认是**10250**,而现在该端口下面已经没有了`/metrics`指标数据了,现在 kubelet 只读的数据接口统一通过**10255**端口进行暴露了,所以我们应该去替换掉这里的端口,但是我们是要替换成**10255**端口吗?不是的,因为我们是要去配置上面通过`node-exporter`抓取到的节点指标数据,而我们上面是不是指定了`hostNetwork=true`,所以在每个节点上就会绑定一个端口**9100**,所以我们应该将这里的**10250**替换成**9100**,但是应该怎样替换呢?
这里我们就需要使用到 Prometheus 提供的`relabel_configs`中的`replace`能力了,relabel 可以在 Prometheus 采集数据之前,通过Target 实例的 Metadata 信息,动态重新写入 Label 的值。除此之外,我们还能根据 Target 实例的 Metadata 信息选择是否采集或者忽略该 Target 实例。比如我们这里就可以去匹配`__address__`这个 Label 标签,然后替换掉其中的端口:
```yaml
......@@ -190,13 +190,13 @@ Get http://10.151.30.57:10250/metrics: net/http: HTTP/1.x transport connection b
action: replace
```
这里就是一个正则表达式,去匹配`__address__`,然后将 host 部分保留下来,port 替换成了**9100**,现在我们重新更新配置文件,执行 reload 操作,然后再去看 Prometheus 的 Dashboard 的 Targets 路径下面 kubernetes-nodes 这个 job 任务是否正常了:
这里就是一个正则表达式,去匹配`__address__`,然后将 host 部分保留下来,port 替换成了**9100**,现在我们重新更新配置文件,执行 reload 操作,然后再去看 Prometheus 的 Dashboard 的 Targets 路径下面 kubernetes-nodes 这个 job 任务是否正常了:
![prometheus nodes target2](./images/promethues-nodes-target2.png)
我们可以看到现在已经正常了,但是还有一个问题就是我们采集的指标数据 Label 标签就只有一个节点的 hostname,这对于我们在进行监控分组分类查询的时候带来了很多不方便的地方,要是我们能够将集群中 Node 节点的 Label 标签也能获取到就很好了。
我们可以看到现在已经正常了,但是还有一个问题就是我们采集的指标数据 Label 标签就只有一个节点的 hostname,这对于我们在进行监控分组分类查询的时候带来了很多不方便的地方,要是我们能够将集群中 Node 节点的 Label 标签也能获取到就很好了。
这里我们可以通过`labelmap`这个属性来将 Kubernetes 的 Label 标签添加为 Prometheus 的指标标签:
这里我们可以通过`labelmap`这个属性来将 Kubernetes 的 Label 标签添加为 Prometheus 的指标标签:
```yaml
- job_name: 'kubernetes-nodes'
kubernetes_sd_configs:
......@@ -211,7 +211,7 @@ Get http://10.151.30.57:10250/metrics: net/http: HTTP/1.x transport connection b
regex: __meta_kubernetes_node_label_(.+)
```
添加了一个 action 为`labelmap`,正则表达式是`__meta_kubernetes_node_label_(.+)`的配置,这里的意思就是表达式中匹配都的数据也添加到指标数据的 Label 标签中去。
添加了一个 action 为`labelmap`,正则表达式是`__meta_kubernetes_node_label_(.+)`的配置,这里的意思就是表达式中匹配都的数据也添加到指标数据的 Label 标签中去。
对于 kubernetes_sd_configs 下面可用的标签如下:
可用元标签:
......@@ -220,12 +220,12 @@ Get http://10.151.30.57:10250/metrics: net/http: HTTP/1.x transport connection b
* __meta_kubernetes_node_label_<labelname>:节点对象中的每个标签
* __meta_kubernetes_node_annotation_<annotationname>:来自节点对象的每个注释
* __meta_kubernetes_node_address_<address_type>:每个节点地址类型的第一个地址(如果存在)
*
*
> 关于 kubernets_sd_configs 更多信息可以查看官方文档:[kubernetes_sd_config](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#%3Ckubernetes_sd_config%3E)
另外由于 kubelet 也自带了一些监控指标数据,就上面我们提到的**10255**端口,所以我们这里也把 kubelet 的监控任务也一并配置上:
另外由于 kubelet 也自带了一些监控指标数据,就上面我们提到的**10255**端口,所以我们这里也把 kubelet 的监控任务也一并配置上:
```yaml
- job_name: 'kubernetes-nodes'
kubernetes_sd_configs:
......@@ -258,11 +258,11 @@ Get http://10.151.30.57:10250/metrics: net/http: HTTP/1.x transport connection b
现在可以看到我们上面添加的`kubernetes-kubelet``kubernetes-nodes`这两个 job 任务都已经配置成功了,而且二者的 Labels 标签都和集群的 node 节点标签保持一致了。
现在我们就可以切换到 Graph 路径下面查看采集的一些指标数据了,比如查询 node_load1 指标:
现在我们就可以切换到 Graph 路径下面查看采集的一些指标数据了,比如查询 node_load1 指标:
![prometheus nodes graph1](./images/prometheus-nodes-graph1.png)
我们可以看到将3个 node 节点对应的 node_load1 指标数据都查询出来了,同样的,我们还可以使用 PromQL 语句来进行更复杂的一些聚合查询操作,还可以根据我们的 Labels 标签对指标数据进行聚合,比如我们这里只查询 node03 节点的数据,可以使用表达式`node_load1{instance="node03"}`来进行查询:
我们可以看到将3个 node 节点对应的 node_load1 指标数据都查询出来了,同样的,我们还可以使用 PromQL 语句来进行更复杂的一些聚合查询操作,还可以根据我们的 Labels 标签对指标数据进行聚合,比如我们这里只查询 node03 节点的数据,可以使用表达式`node_load1{instance="node03"}`来进行查询:
![prometheus nodes graph2](./images/prometheus-nodes-graph2.png)
......
......@@ -27,7 +27,7 @@
上面的配置和我们之前配置 node-exporter 的时候几乎是一样的,区别是我们这里使用了 https 的协议,另外需要注意的是配置了 ca.cart 和 token 这两个文件,这两个文件是 Pod 启动后自动注入进来的,通过这两个文件我们可以在 Pod 中访问 apiserver,比如我们这里的`__address__`不在是 nodeip 了,而是 kubernetes 在集群中的服务地址,然后加上`__metrics_path__`的访问路径:`/api/v1/nodes/${1}/proxy/metrics/cadvisor`,现在同样更新下配置,然后查看 Targets 路径:
![prometheus cAdvisor](./images/prometheus-cadvisor.png)
然后我们可以切换到 Graph 路径下面查询容器相关数据,比如我们这里来查询集群中所有 Pod 的 CPU 使用情况,这里用的数据指标是 container_cpu_usage_seconds_total,然后去除一些无效的数据,查询1分钟内的数据,由于查询到的数据都是容器相关的,最好要安装 Pod 来进行聚合,对应的`promQL`语句如下:
然后我们可以切换到 Graph 路径下面查询容器相关数据,比如我们这里来查询集群中所有 Pod 的 CPU 使用情况,这里用的数据指标是 container_cpu_usage_seconds_total,然后去除一些无效的数据,查询1分钟内的数据,由于查询到的数据都是容器相关的,最好要安装 Pod 来进行聚合,对应的`promQL`语句如下:
```
sum by (pod_name)(rate(container_cpu_usage_seconds_total{image!="", pod_name!=""}[1m] ))
```
......@@ -64,7 +64,7 @@ $ curl -X POST "http://10.102.74.90:9090/-/reload"
更新完成后,我们再去查看 Prometheus 的 Dashboard 的 target 页面:
![prometheus apiserver](./images/prometheus-apiserver.png)
我们可以看到 kubernetes-apiservers 下面出现了很多实例,这是因为这里我们使用的是 Endpoints 类型的服务发现,所以 Prometheus 把所有的 Endpoints 服务都抓取过来了,同样的,上面我们需要的服务名为`kubernetes`这个 apiserver 的服务也在这个列表之中,那么我们应该怎样来过滤出这个服务来呢?还记得上节课的`relabel_configs`吗?没错,同样我们需要使用这个配置,只是我们这里不是使用`replace`这个动作了,而是`keep`,就是只把符合我们要求的给保留下来,哪些才是符合我们要求的呢?我们可以把鼠标放置在任意一个 target 上,可以查看到`Before relabeling`里面所有的元数据,比如我们要过滤的服务是 default 这个 namespace 下面,服务名为 kubernetes 的元数据,所以这里我们就可以根据对应的`__meta_kubernetes_namespace``__meta_kubernetes_service_name`这两个元数据来 relabel
我们可以看到 kubernetes-apiservers 下面出现了很多实例,这是因为这里我们使用的是 Endpoints 类型的服务发现,所以 Prometheus 把所有的 Endpoints 服务都抓取过来了,同样的,上面我们需要的服务名为`kubernetes`这个 apiserver 的服务也在这个列表之中,那么我们应该怎样来过滤出这个服务来呢?还记得上节课的`relabel_configs`吗?没错,同样我们需要使用这个配置,只是我们这里不是使用`replace`这个动作了,而是`keep`,就是只把符合我们要求的给保留下来,哪些才是符合我们要求的呢?我们可以把鼠标放置在任意一个 target 上,可以查看到`Before relabeling`里面所有的元数据,比如我们要过滤的服务是 default 这个 namespace 下面,服务名为 kubernetes 的元数据,所以这里我们就可以根据对应的`__meta_kubernetes_namespace``__meta_kubernetes_service_name`这两个元数据来 relabel
![prometheus before lable](./images/promtheus-before-label.png)
......@@ -92,14 +92,14 @@ $ curl -X POST "http://10.102.74.90:9090/-/reload"
sum(rate(apiserver_request_count[1m]))
```
> 这里我们使用到了 promql 里面的 rate 和 sum函数,表示的意思是 apiserver 在1分钟内总的请求数。
> 这里我们使用到了 promql 里面的 rate 和 sum函数,表示的意思是 apiserver 在1分钟内总的请求数。
![apiserver request count](./images/prometheus-apiserver-request.png)
这样我们就完成了对 Kubernetes APIServer 的监控。
另外如果我们要来监控其他系统组件,比如 kube-controller-manager、kube-scheduler 的话应该怎么做呢?由于 apiserver 服务 namespace 在 default 使用默认的 Service kubernetes,而其余组件服务在 kube-system 这个 namespace 下面,如果我们想要来监控这些组件的话,需要手动创建单独的 Service,其中 kube-sheduler 的指标数据端口为 10251,kube-controller-manager 对应的端口为 10252,大家可以尝试下自己来配置下这几个系统组件。
另外如果我们要来监控其他系统组件,比如 kube-controller-manager、kube-scheduler 的话应该怎么做呢?由于 apiserver 服务 namespace 在 default 使用默认的 Service kubernetes,而其余组件服务在 kube-system 这个 namespace 下面,如果我们想要来监控这些组件的话,需要手动创建单独的 Service,其中 kube-sheduler 的指标数据端口为 10251,kube-controller-manager 对应的端口为 10252,大家可以尝试下自己来配置下这几个系统组件。
## Service 的监控
上面的 apiserver 实际上是一种特殊的 Service,现在我们同样来配置一个任务用来专门发现普通类型的 Service:
......@@ -134,12 +134,12 @@ sum(rate(apiserver_request_count[1m]))
target_label: kubernetes_name
```
注意我们这里在`relabel_configs`区域做了大量的配置,特别是第一个保留`__meta_kubernetes_service_annotation_prometheus_io_scrape``true`的才保留下来,这就是说要想自动发现集群中的 Service,就需要我们在 Service 的`annotation`区域添加`prometheus.io/scrape=true`的声明,现在我们先将上面的配置更新,查看下效果:
注意我们这里`relabel_configs`区域做了大量的配置,特别是第一个保留`__meta_kubernetes_service_annotation_prometheus_io_scrape``true`的才保留下来,这就是说要想自动发现集群中的 Service,就需要我们在 Service 的`annotation`区域添加`prometheus.io/scrape=true`的声明,现在我们先将上面的配置更新,查看下效果:
![service endpoints](./images/prometheus-service-endpoints.png)
我们可以看到`kubernetes-service-endpoints`这一个任务下面只发现了一个服务,这是因为我们在`relabel_configs`中过滤了 annotation 有`prometheus.io/scrape=true`的 Service,而现在我们系统中只有这样一个服务符合要求,所以只出现了一个实例。
我们可以看到`kubernetes-service-endpoints`这一个任务下面只发现了一个服务,这是因为我们在`relabel_configs`中过滤了 annotation 有`prometheus.io/scrape=true`的 Service,而现在我们系统中只有这样一个服务符合要求,所以只出现了一个实例。
现在我们在之前创建的 redis 这个 Service 中添加上`prometheus.io/scrape=true`这个 annotation:(prome-redis-exporter.yaml)
```yaml
......@@ -175,7 +175,7 @@ service "redis" changed
![kubernetes service endpoints](./images/prometheus-service-endpoints2.png)
这样以后我们有了新的服务,服务本身提供了`/metrics`接口,我们就完全不需要用静态的方式去配置了,到这里我们就可以将之前配置的 redis 的静态配置去掉了。
这样以后我们有了新的服务,服务本身提供了`/metrics`接口,我们就完全不需要用静态的方式去配置了,到这里我们就可以将之前配置的 redis 的静态配置去掉了。
> 大家可以尝试去将之前配置的 traefik 服务用动态发现的方式重新配置到上面的 service-endpoints 中。
......
......@@ -167,7 +167,7 @@ mkdir: cannot create directory '/var/lib/grafana/plugins': Permission denied
```yaml
apiVersion: batch/v1
kind: Job
metadata:
metadata:
name: grafana-chown
namespace: kube-ops
spec:
......@@ -215,7 +215,7 @@ $ kubectl logs -f grafana-79477fbb7c-2mb84 -n kube-ops
t=2018-11-14T19:57:31+0000 lvl=info msg="Starting Grafana" logger=server version=5.3.4 commit=69630b9 compiled=2018-11-13T12:19:12+0000
......
logger=settings var="GF_SECURITY_ADMIN_USER=admin"
t=2018-11-14T19:57:31+0000 lvl=info msg="Config overridden from Environment variable"
t=2018-11-14T19:57:31+0000 lvl=info msg="Config overridden from Environment variable"
......
t=2018-11-14T19:57:32+0000 lvl=info msg="Initializing Stream Manager"
t=2018-11-14T19:57:32+0000 lvl=info msg="HTTP Server Listen" logger=http.server address=0.0.0.0:3000 protocol=http subUrl= socket=
......@@ -239,30 +239,30 @@ grafana NodePort 10.97.46.27 <none> 3000:30105/TCP
在上面的首页中我们可以看到已经安装了 Grafana,接下来点击`Add data source`进入添加数据源界面。
### 数据源
我们这个地方配置的数据源是 Prometheus,所以选择这个 Type 即可,给改数据源添加一个 name:prometheus-ds,最主要的是下面`HTTP`区域是配置数据源的访问模式。
我们这个地方配置的数据源是 Prometheus,所以选择这个 Type 即可,给改数据源添加一个 name:prometheus-ds,最主要的是下面`HTTP`区域是配置数据源的访问模式。
访问模式是用来控制如何处理对数据源的请求的:
* 服务器(Server)访问模式(默认):所有请求都将从浏览器发送到 Grafana 后端的服务器,后者又将请求转发到数据源,通过这种方式可以避免一些跨域问题,其实就是在 Grafana 后端做了一次转发,需要从Grafana 后端服务器访问该 URL。
* 服务器(Server)访问模式(默认):所有请求都将从浏览器发送到 Grafana 后端的服务器,后者又将请求转发到数据源,通过这种方式可以避免一些跨域问题,其实就是在 Grafana 后端做了一次转发,需要从Grafana 后端服务器访问该 URL。
* 浏览器(Browser)访问模式:所有请求都将从浏览器直接发送到数据源,但是有可能会有一些跨域的限制,使用此访问模式,需要从浏览器直接访问该 URL。
由于我们这个地方 Prometheus 通过 NodePort 的方式的对外暴露的服务,所以我们这个地方是不是可以使用浏览器访问模式直接访问 Prometheus 的外网地址,但是这种方式显然不是最好的,相当于走的是外网,而我们这里 Prometheus 和 Grafana 都处于 kube-ops 这同一个 namespace 下面,是不是在集群内部直接通过 DNS 的形式就可以访问了,而且还都是走的内网流量,所以我们这里用服务器访问模式显然更好,数据源地址:`http://prometheus:9090`(因为在同一个 namespace 下面所以直接用 Service 名也可以),然后其他的配置信息就根据实际情况了,比如 Auth 认证,我们这里没有,所以跳过即可,点击最下方的`Save & Test`提示成功证明我们的数据源配置正确:
由于我们这个地方 Prometheus 通过 NodePort 的方式的对外暴露的服务,所以我们这个地方是不是可以使用浏览器访问模式直接访问 Prometheus 的外网地址,但是这种方式显然不是最好的,相当于走的是外网,而我们这里 Prometheus 和 Grafana 都处于 kube-ops 这同一个 namespace 下面,是不是在集群内部直接通过 DNS 的形式就可以访问了,而且还都是走的内网流量,所以我们这里用服务器访问模式显然更好,数据源地址:`http://prometheus:9090`(因为在同一个 namespace 下面所以直接用 Service 名也可以),然后其他的配置信息就根据实际情况了,比如 Auth 认证,我们这里没有,所以跳过即可,点击最下方的`Save & Test`提示成功证明我们的数据源配置正确:
![grafana datasource](./images/grafana-prometheus-ds.png)
数据源添加完成后,就可以来添加 Dashboard 了。
### Dashboard
同样,切换到主页,我们可以根据自己的需求手动新建一个 Dashboard,除此之外,grafana 的官方网站上还有很多公共的 Dashboard 可以供我们使用,我们这里可以使用[Kubernetes cluster monitoring (via Prometheus)(dashboard id 为162)](https://grafana.com/dashboards/162/revisions)这个 Dashboard 来展示 Kubernetes 集群的监控信息,在左侧侧边栏 Create 中点击`import`导入:
同样,切换到主页,我们可以根据自己的需求手动新建一个 Dashboard,除此之外,grafana 的官方网站上还有很多公共的 Dashboard 可以供我们使用,我们这里可以使用[Kubernetes cluster monitoring (via Prometheus)(dashboard id 为162)](https://grafana.com/dashboards/162/revisions)这个 Dashboard 来展示 Kubernetes 集群的监控信息,在左侧侧边栏 Create 中点击`import`导入:
![grafana import](./images/grafana-dashboard-import.png)
我们可以将上面编号`162`的 dashboard 下载到本地,然后这里重新上传即可,也可以在上面的文本框中直接输入`162`编号回车即可,导入这个 dashboard:
![grafana import2](./images/grafana-dashboard-import2.png)
需要注意的是在执行上面的 import 之前要记得选择我们的`prometheus-ds`这个名字的数据源,执行`import`操作,就可以进入到 dashboard 页面:
![grafana k8s cluster monitor](./images/grafana-k8s-monitor.png)
![grafana k8s cluster monitor](./images/grafana-k8s-monitor.png)
我们可以看到 dashboard 页面上出现了很多漂亮的图表,但是看上去数据不正常,这是因为这个 dashboard 里面需要的数据指标名称和我们 Prometheus 里面采集到的数据指标不一致造成的,比如,第一个`Cluster memory usage(集群内存使用情况)`,我们可以点击标题 -> Edit,进入编辑这个图表的编辑页面:
我们可以看到 dashboard 页面上出现了很多漂亮的图表,但是看上去数据不正常,这是因为这个 dashboard 里面需要的数据指标名称和我们 Prometheus 里面采集到的数据指标不一致造成的,比如,第一个`Cluster memory usage(集群内存使用情况)`,我们可以点击标题 -> Edit,进入编辑这个图表的编辑页面:
![grafana dashboard edit](./images/grafana-dashboard-edit.png)
进入编辑页面我们就可以看到这个图表的查询语句:
......@@ -277,11 +277,11 @@ grafana NodePort 10.97.46.27 <none> 3000:30105/TCP
(sum(node_memory_MemTotal_bytes) - sum(node_memory_MemFree_bytes + node_memory_Buffers_bytes+node_memory_Cached_bytes)) / sum(node_memory_MemTotal_bytes) * 100
```
这个语句的意思就是`(整个集群的内存-(整个集群剩余的内存以及Buffer和Cached))/整个集群的内存`,简单来说就是总的集群内存使用百分比。将上面 grafana 的`promQL`语句替换掉,就可以看到图表正常了:
![grafana table](./images/grafana-table.png)
这个语句的意思就是`(整个集群的内存-(整个集群剩余的内存以及Buffer和Cached))/整个集群的内存`,简单来说就是总的集群内存使用百分比。将上面 grafana 的`promQL`语句替换掉,就可以看到图表正常了:
![grafana table](./images/grafana-table.png)
同样的,我们可以更改后面的 CPU 和 FileSystem 的使用率:
同样的,我们可以更改后面的 CPU 和 FileSystem 的使用率:
![grafana cluster table](./images/grafana-cluster-table.png)
......@@ -291,10 +291,10 @@ sum by (pod_name)(rate(container_cpu_usage_seconds_total{image!="", pod_name!=""
```
按照上面的方法替换 grafana 中的 dashboard 图表中的查询语句:
![grafana cpu usage](./images/grafana-cpu-usage.png)
![grafana cpu usage](./images/grafana-cpu-usage.png)
其他的也按照我们的实际需求重新编辑下就可以,下图是最终整个 dashboard 的效果图:
其他的也按照我们的实际需求重新编辑下就可以,下图是最终整个 dashboard 的效果图:
![grafana k8s cluster dashboard](./images/grafana-k8s-cluster-dashboard.png)
......@@ -324,7 +324,7 @@ Restart grafana after installing plugins . <service grafana-server restart>
grafana@grafana-79477fbb7c-v4prs:/usr/share/grafana$
```
安装完成后需要重启 grafana 才会生效,我们这里直接删除 Pod,重建即可,然后回到 grafana 页面中,切换到 plugins 页面可以发现下面多了一个 Kubernetes 的插件,点击进来启用即可,然后点击`Next up`旁边的链接配置集群
安装完成后需要重启 grafana 才会生效,我们这里直接删除 Pod,重建即可,然后回到 grafana 页面中,切换到 plugins 页面可以发现下面多了一个 Kubernetes 的插件,点击进来启用即可,然后点击`Next up`旁边的链接配置集群
![grafana k8s plugins](./images/grafana-k8s-plugin.png)
......@@ -336,13 +336,13 @@ grafana@grafana-79477fbb7c-v4prs:/usr/share/grafana$
> 另外需要将解码过后的`\n`换成换行符,不然认证会失败。
配置完成后,可以直接点击`Deploy`(实际上前面的课程中我们都已经部署过相关的资源了),然后点击`Save`,就可以获取到集群的监控资源信息了。
配置完成后,可以直接点击`Deploy`(实际上前面的课程中我们都已经部署过相关的资源了),然后点击`Save`,就可以获取到集群的监控资源信息了。
![grafana k8s plugins](./images/grafana-k8s-plugin2.png)
可以看到上面展示了整个集群的状态,可以查看上面的一些 Dashboard:
可以看到上面展示了整个集群的状态,可以查看上面的一些 Dashboard:
![grafana k8s cluster dashboard](./images/grafana-k8s-plugin-cluster.png)
## 报警
grafana 4 版本以上就支持了报警功能,这使得我们利用 grafana 作为监控面板更为完整,因为报警是监控系统中必不可少的环节,grafana 支持很多种形式的报警功能,比如 email、钉钉、slack、webhook 等等,我们这里来测试下 email 和 钉钉。
......@@ -393,9 +393,9 @@ $ kubectl apply -f grafana-deploy.yaml
![grafana email alert](./images/grafana-alert-email.png)
发送测试后,正常情况下就可以收到测试报警邮件:
发送测试后,正常情况下就可以收到测试报警邮件:
![grafana alert email](./images/grafana-email-alert.png)
![grafana alert email](./images/grafana-email-alert.png)
### 钉钉报警
......@@ -403,18 +403,18 @@ $ kubectl apply -f grafana-deploy.yaml
![grafana add dingtalk robot](./images/grafana-add-dingtalk-robot.png)
添加完成后可以得到一个 webhook 的地址,然后将这个 webhook 地址添加到上面 grafana webui 的报警测试页面进行测试,就可以在钉钉群里面收到报警测试信息了:
添加完成后可以得到一个 webhook 的地址,然后将这个 webhook 地址添加到上面 grafana webui 的报警测试页面进行测试,就可以在钉钉群里面收到报警测试信息了:
![grafana dingtalk alert](./images/grafana-alert-dingtalk-robot.png)
### 配置
目前只有 Graph 支持报警功能,所以我们选择 Graph 相关图表,点击编辑,进入 Graph 编辑页面可以看到有一个 Alert 模块,切换过来创建报警:
![grafana graph alert](./images/grafana-graph-alert.png)
![grafana graph alert](./images/grafana-graph-alert.png)
然后配置相关参数:
* 1、Alert 名称,可以自定义。
* 2、执行的频率,这里我选择每60s检测一次。
* 1、Alert 名称,可以自定义。
* 2、执行的频率,这里我选择每60s检测一次。
* 3、判断标准,默认是 avg,这里是下拉框,自己按需求选择。
* 4、query(A,5m,now),字母A代表选择的metrics 中设置的 sql,也可以选择其它在 metrics中设置的,但这里是单选。`5m`代表从现在起往之前的五分钟,即`5m`之前的那个点为时间的起始点,`now`为时间的结束点,此外这里可以自己手动输入时间。
* 5、设置的预警临界点,这里手动输入,和6是同样功能,6可以手动移动,两种操作是等同的。
......@@ -422,7 +422,7 @@ $ kubectl apply -f grafana-deploy.yaml
然后需要设置报警发送信息,点击侧边的`Notifications`:
![grafana graph notify](./images/grafana-graph-notify.png)
其中`Send to`就是前面我们配置过的发送邮件和钉钉的报警频道的名称。
配置完成后需要保存下这个 graph,否则发送报警可能会失败,然后点击 Alert 区域的`Test Rule`可以来测试报警规则,然后邮件和钉钉正常来说就可以收到报警信息了。
......@@ -435,6 +435,6 @@ $ kubectl apply -f grafana-deploy.yaml
![grafana test rule dingtalk](./images/grafana-dingtalk-alert2.png)
到这里就完成了使用 grafana 来展示 Kubernetes 集群的监控图表信息以及报警配置,但是我们明显可以感觉到 grafana 的优势在于图表的展示,报警功能有点弱,所以一般来说,在生产环境我们不会直接使用 grafana 的报警功能,更多的是使用功能更加强大的 `AlertManager`,下节课我们再来和大家介绍了。
此差异已折叠。
# 58. Prometheus Operator 的安装
前面的课程中我们学习了用自定义的方式来对 Kubernetes 集群进行监控,但是还是有一些缺陷,比如 Prometheus、AlertManager 这些组件服务本身的高可用,当然我们也完全可以用自定义的方式来实现这些需求,我们也知道 Promethues 在代码上就已经对 Kubernetes 有了原生的支持,可以通过服务发现的形式来自动监控集群,因此我们可以使用另外一种更加高级的方式来部署 Prometheus:`Operator` 框架。
## Operator
`Operator`是由[CoreOS](https://coreos.com/)公司开发的,用来扩展 Kubernetes API,特定的应用程序控制器,它用来创建、配置和管理复杂的有状态应用,如数据库、缓存和监控系统。`Operator`基于 Kubernetes 的资源和控制器概念之上构建,但同时又包含了应用程序特定的一些专业知识,比如创建一个数据库的`Operator`,则必须对创建的数据库的各种运维方式非常了解,创建`Operator`的关键是`CRD`(自定义资源)的设计。
> `CRD`是对 Kubernetes API 的扩展,Kubernetes 中的每个资源都是一个 API 对象的集合,例如我们在YAML文件里定义的那些`spec`都是对 Kubernetes 中的资源对象的定义,所有的自定义资源可以跟 Kubernetes 中内建的资源一样使用 kubectl 操作。
`Operator`是将运维人员对软件操作的知识给代码化,同时利用 Kubernetes 强大的抽象来管理大规模的软件应用。目前`CoreOS`官方提供了几种`Operator`的实现,其中就包括我们今天的主角:`Prometheus Operator``Operator`的核心实现就是基于 Kubernetes 的以下两个概念:
* 资源:对象的状态定义
* 控制器:观测、分析和行动,以调节资源的分布
当然我们如果有对应的需求也完全可以自己去实现一个`Operator`,接下来我们就来给大家详细介绍下`Prometheus-Operator`的使用方法。
## 介绍
首先我们先来了解下`Prometheus-Operator`的架构图:
![promtheus opeator](./images/prometheus-operator.png)
上图是`Prometheus-Operator`官方提供的架构图,其中`Operator`是最核心的部分,作为一个控制器,他会去创建`Prometheus``ServiceMonitor``AlertManager`以及`PrometheusRule`4个`CRD`资源对象,然后会一直监控并维持这4个资源对象的状态。
其中创建的`prometheus`这种资源对象就是作为`Prometheus Server`存在,而`ServiceMonitor`就是`exporter`的各种抽象,`exporter`前面我们已经学习了,是用来提供专门提供`metrics`数据接口的工具,`Prometheus`就是通过`ServiceMonitor`提供的`metrics`数据接口去 pull 数据的,当然`alertmanager`这种资源对象就是对应的`AlertManager`的抽象,而`PrometheusRule`是用来被`Prometheus`实例使用的报警规则文件。
这样我们要在集群中监控什么数据,就变成了直接去操作 Kubernetes 集群的资源对象了,是不是方便很多了。上图中的 Service 和 ServiceMonitor 都是 Kubernetes 的资源,一个 ServiceMonitor 可以通过 labelSelector 的方式去匹配一类 Service,Prometheus 也可以通过 labelSelector 去匹配多个ServiceMonitor。
## 安装
我们这里直接通过 Prometheus-Operator 的源码来进行安装,当然也可以用 Helm 来进行一键安装,我们采用源码安装可以去了解更多的实现细节。首页将源码 Clone 下来:
```shell
$ git clone https://github.com/coreos/prometheus-operator
$ cd contrib/kube-prometheus/manifests/
$ ls
00namespace-namespace.yaml node-exporter-clusterRole.yaml
0prometheus-operator-0alertmanagerCustomResourceDefinition.yaml node-exporter-daemonset.yaml
......
```
进入到 manifests 目录下面,这个目录下面包含我们所有的资源清单文件,我们需要对其中的文件 prometheus-serviceMonitorKubelet.yaml 进行简单的修改,因为默认情况下,这个 ServiceMonitor 是关联的 kubelet 的10250端口去采集的节点数据,而我们前面说过为了安全,这个 metrics 数据已经迁移到10255这个只读端口上面去了,我们只需要将文件中的`https-metrics`更改成`http-metrics`即可,这个在 Prometheus-Operator 对节点端点同步的代码中有相关定义,感兴趣的可以[点此查看完整代码](https://github.com/coreos/prometheus-operator/blob/7a8bc75512e39a984f46b2788b5fa9bf64999571/pkg/prometheus/operator.go#L400)
```go
Subsets: []v1.EndpointSubset{
{
Ports: []v1.EndpointPort{
{
Name: "https-metrics",
Port: 10250,
},
{
Name: "http-metrics",
Port: 10255,
},
{
Name: "cadvisor",
Port: 4194,
},
},
},
},
```
修改完成后,直接在该文件夹下面执行创建资源命令即可:
```shell
$ kubectl apply -f .
```
部署完成后,会创建一个名为`monitoring`的 namespace,所以资源对象对将部署在改命名空间下面,此外 Operator 会自动创建4个 CRD 资源对象:
```shell
$ kubectl get crd |grep coreos
alertmanagers.monitoring.coreos.com 5d
prometheuses.monitoring.coreos.com 5d
prometheusrules.monitoring.coreos.com 5d
servicemonitors.monitoring.coreos.com 5d
```
可以在 monitoring 命名空间下面查看所有的 Pod,其中 alertmanager 和 prometheus 是用 StatefulSet 控制器管理的,其中还有一个比较核心的 prometheus-operator 的 Pod,用来控制其他资源对象和监听对象变化的:
```shell
$ kubectl get pods -n monitoring
NAME READY STATUS RESTARTS AGE
alertmanager-main-0 2/2 Running 0 21h
alertmanager-main-1 2/2 Running 0 21h
alertmanager-main-2 2/2 Running 0 21h
grafana-df9bfd765-f4dvw 1/1 Running 0 22h
kube-state-metrics-77c9658489-ntj66 4/4 Running 0 20h
node-exporter-4sr7f 2/2 Running 0 21h
node-exporter-9mh2r 2/2 Running 0 21h
node-exporter-m2gkp 2/2 Running 0 21h
prometheus-adapter-dc548cc6-r6lhb 1/1 Running 0 22h
prometheus-k8s-0 3/3 Running 1 21h
prometheus-k8s-1 3/3 Running 1 21h
prometheus-operator-bdf79ff67-9dc48 1/1 Running 0 21h
```
查看创建的 Service:
```shell
kubectl get svc -n monitoring
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
alertmanager-main ClusterIP 10.110.204.224 <none> 9093/TCP 23h
alertmanager-operated ClusterIP None <none> 9093/TCP,6783/TCP 23h
grafana ClusterIP 10.98.191.31 <none> 3000/TCP 23h
kube-state-metrics ClusterIP None <none> 8443/TCP,9443/TCP 23h
node-exporter ClusterIP None <none> 9100/TCP 23h
prometheus-adapter ClusterIP 10.107.201.172 <none> 443/TCP 23h
prometheus-k8s ClusterIP 10.107.105.53 <none> 9090/TCP 23h
prometheus-operated ClusterIP None <none> 9090/TCP 23h
prometheus-operator ClusterIP None <none> 8080/TCP 23h
```
可以看到上面针对 grafana 和 prometheus 都创建了一个类型为 ClusterIP 的 Service,当然如果我们想要在外网访问这两个服务的话可以通过创建对应的 Ingress 对象或者使用 NodePort 类型的 Service,我们这里为了简单,直接使用 NodePort 类型的服务即可,编辑 grafana 和 prometheus-k8s 这两个 Service,将服务类型更改为 NodePort:
```shell
$ kubectl edit svc grafana -n monitoring
$ kubectl edit svc prometheus-k8s -n monitoring
$ kubectl get svc -n monitoring
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
grafana NodePort 10.98.191.31 <none> 3000:32333/TCP 23h
prometheus-k8s NodePort 10.107.105.53 <none> 9090:30166/TCP 23h
......
```
更改完成后,我们就可以通过<NodeIP:NodePort>去访问上面的两个服务了,比如查看 prometheus 的 targets 页面:
![promtheus operator targets](./images/promethues-operator-targets.png)
## 配置
我们可以看到大部分的配置都是正常的,只有两三个没有管理到对应的监控目标,比如 kube-controller-manager 和 kube-scheduler 这两个系统组件,这就和 ServiceMonitor 的定义有关系了,我们先来查看下 kube-scheduler 组件对应的 ServiceMonitor 资源的定义:(prometheus-serviceMonitorKubeScheduler.yaml)
```yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
labels:
k8s-app: kube-scheduler
name: kube-scheduler
namespace: monitoring
spec:
endpoints:
- interval: 30s # 每30s获取一次信息
port: http-metrics # 对应service的端口名
jobLabel: k8s-app
namespaceSelector: # 表示去匹配某一命名空间中的service,如果想从所有的namespace中匹配用any: true
matchNames:
- kube-system
selector: # 匹配的 Service 的labels,如果使用mathLabels,则下面的所有标签都匹配时才会匹配该service,如果使用matchExpressions,则至少匹配一个标签的service都会被选择
matchLabels:
k8s-app: kube-scheduler
```
上面是一个典型的 ServiceMonitor 资源文件的声明方式,上面我们通过`selector.matchLabels`在 kube-system 这个命名空间下面匹配具有`k8s-app=kube-scheduler`这样的 Service,但是我们系统中根本就没有对应的 Service,所以我们需要手动创建一个 Service:(prometheus-kubeSchedulerService.yaml)
```yaml
apiVersion: v1
kind: Service
metadata:
namespace: kube-system
name: kube-scheduler
labels:
k8s-app: kube-scheduler
spec:
selector:
component: kube-scheduler
ports:
- name: http-metrics
port: 10251
targetPort: 10251
protocol: TCP
```
> 10251是`kube-scheduler`组件 metrics 数据所在的端口,10252是`kube-controller-manager`组件的监控数据所在端口。
其中最重要的是上面 labels 和 selector 部分,labels 区域的配置必须和我们上面的 ServiceMonitor 对象中的 selector 保持一致,`selector`下面配置的是`component=kube-scheduler`,为什么会是这个 label 标签呢,我们可以去 describe 下 kube-scheduelr 这个 Pod:
```shell
$ kubectl describe pod kube-scheduler-master -n kube-system
Name: kube-scheduler-master
Namespace: kube-system
Node: master/10.151.30.57
Start Time: Sun, 05 Aug 2018 18:13:32 +0800
Labels: component=kube-scheduler
tier=control-plane
......
```
我们可以看到这个 Pod 具有`component=kube-scheduler``tier=control-plane`这两个标签,而前面这个标签具有更唯一的特性,所以使用前面这个标签较好,这样上面创建的 Service 就可以和我们的 Pod 进行关联了,直接创建即可:
```shell
$ kubectl create -f prometheus-kubeSchedulerService.yaml
$ kubectl get svc -n kube-system -l k8s-app=kube-scheduler
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-scheduler ClusterIP 10.102.119.231 <none> 10251/TCP 18m
```
创建完成后,隔一小会儿后去 prometheus 查看 targets 下面 kube-scheduler 的状态:
![promethus kube-scheduler error](./images/promethues-operator-kube-scheduler-error.png)
我们可以看到现在已经发现了 target,但是抓取数据结果出错了,这个错误是因为我们集群是使用 kubeadm 搭建的,其中 kube-scheduler 默认是绑定在`127.0.0.1`上面的,而上面我们这个地方是想通过节点的 IP 去访问,所以访问被拒绝了,我们只要把 kube-scheduler 绑定的地址更改成`0.0.0.0`即可满足要求,由于 kube-scheduler 是以静态 Pod 的形式运行在集群中的,所以我们只需要更改静态 Pod 目录下面对应的 YAML 文件即可:
```shell
$ ls /etc/kubernetes/manifests/
etcd.yaml kube-apiserver.yaml kube-controller-manager.yaml kube-scheduler.yaml
```
将 kube-scheduler.yaml 文件中`-command``--address`地址更改成`0.0.0.0`
```yaml
containers:
- command:
- kube-scheduler
- --leader-elect=true
- --kubeconfig=/etc/kubernetes/scheduler.conf
- --address=0.0.0.0
```
修改完成后我们将该文件从当前文件夹中移除,隔一会儿再移回该目录,就可以自动更新了,然后再去看 prometheus 中 kube-scheduler 这个 target 是否已经正常了:
![promethues-operator-kube-scheduler](./images/promethues-operator-kube-scheduler.png)
大家可以按照上面的方法尝试去修复下 kube-controller-manager 组件的监控。
上面的监控数据配置完成后,现在我们可以去查看下 grafana 下面的 dashboard,同样使用上面的 NodePort 访问即可,第一次登录使用 admin:admin 登录即可,进入首页后,可以发现已经和我们的 Prometheus 数据源关联上了,正常来说可以看到一些监控图表了:
![promethues-operator-grafana](./images/promethues-operator-grafana.png)
下节课我们再来和大家介绍怎样来完全自定义一个 ServiceMonitor 以及 AlertManager 相关的配置。
$ kubectl -n monitoring create secret generic etcd-certs --from-file=/etc/kubernetes/pki/etcd/healthcheck-client.crt --from-file=/etc/kubernetes/pki/etcd/healthcheck-client.key --from-file=/etc/kubernetes/pki/etcd/ca.crt
secret "etcd-certs" created
kubectl exec -it prometheus-k8s-0 /bin/sh -n monitoring
Defaulting container name to prometheus.
Use 'kubectl describe pod/prometheus-k8s-0 -n monitoring' to see all of the containers in this pod.
/ $ ls /etc/prometheus/secrets/etcd-certs/
ca.crt server.crt server.key
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: etcd-k8s
namespace: monitoring
labels:
k8s-app: etcd-k8s
spec:
jobLabel: k8s-app
endpoints:
- port: port
interval: 30s
scheme: https
tlsConfig:
caFile: /etc/prometheus/secrets/etcd-certs/ca.crt
certFile: /etc/kubernetes/pki/etcd/healthcheck-client.crt
keyFile: /etc/kubernetes/pki/etcd/healthcheck-client.key
insecureSkipVerify: true
selector:
matchLabels:
k8s-app: etcd
namespaceSelector:
matchNames:
- kube-system
apiVersion: v1
kind: Service
metadata:
name: etcd-k8s
namespace: kube-system
labels:
k8s-app: etcd
spec:
type: ClusterIP
clusterIP: None
ports:
- name: port
port: 12379
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: etcd-k8s
namespace: kube-system
labels:
k8s-app: etcd
subsets:
- addresses:
- ip: 10.151.30.57
nodeName: etc-master
ports:
- name: port
port: 2379
protocol: TCP
\ No newline at end of file
此差异已折叠。
......@@ -256,9 +256,9 @@ metadata:
......
```
有的同学可能已经发现了,我们这个地方的应用是依赖于 mysql 的,那么为什么我们只是把 wordpress 相关的数据来做了定制呢?当然我们也可以在我们的这个 chart 中来定制 mysql,但是这却不是最好的方法,最好的方法是让我们去依赖一个独立的 mysql chart,这样可以将 wordpress 和 mysql 之间的耦合关系降低,后面我们再和大家来看看怎样解耦。
有的同学可能已经发现了,我们这个地方的应用是依赖于 mysql 的,那么为什么我们只是把 wordpress 相关的数据来做了定制呢?当然我们也可以在我们的这个 chart 中来定制 mysql,但是这却不是最好的方法,最好的方法是让我们去依赖一个独立的 mysql chart,这样可以将 wordpress 和 mysql 之间的耦合关系降低,后面我们再和大家来看看怎样解耦。
为了不影响对 wordpress 的操作,我们可以临时将 templates 目录下面的 mysql 的资源对象单独提取出来,比如我们这里统一放到一个叫 mysql.yaml 的资源文件中,现在我们的结构就是这样的了:
为了不影响对 wordpress 的操作,我们可以临时将 templates 目录下面的 mysql 的资源对象单独提取出来,比如我们这里统一放到一个叫 mysql.yaml 的资源文件中,现在我们的结构就是这样的了:
```shell
$ tree .
.
......@@ -275,7 +275,7 @@ $ tree .
```
### 镜像
现在我们使用的镜像还是固定的`wordpress:latest`,为了方便其他人使用,我们在编写 chart 包的时候会提供一个定制参数值,可以自由指定使用的镜像,包括 tag 版本。我们可以先去添加 values.yaml 文件中的内容:
现在我们使用的镜像还是固定的`wordpress:latest`,为了方便其他人使用,我们在编写 chart 包的时候会提供一个定制参数值,可以自由指定使用的镜像,包括 tag 版本。我们可以先去添加 values.yaml 文件中的内容:
```yaml
nameOverride: ""
fullnameOverride: ""
......@@ -299,7 +299,7 @@ image:
# - myRegistrKeySecretName
```
我们在 values.yaml 文件中添加了一个 image 的对象,里面包含仓库地址、镜像名称、镜像版本,这是因为一个标准的镜像就包含这3个部分,每一个部分都是可能被定制的,然后指定一个镜像拉取策略的参数 imagePullPolicy,还不算完,为什么呢?如果我们要使用的镜像是一个私有仓库的镜像怎么办?所以我们这里还预留了一个参数:pullSecrets,用来指定私有仓库地址的 Secrets,现在我们再去修改 templates/deployment.yaml 文件就简单很多了:
我们在 values.yaml 文件中添加了一个 image 的对象,里面包含仓库地址、镜像名称、镜像版本,这是因为一个标准的镜像就包含这3个部分,每一个部分都是可能被定制的,然后指定一个镜像拉取策略的参数 imagePullPolicy,还不算完,为什么呢?如果我们要使用的镜像是一个私有仓库的镜像怎么办?所以我们这里还预留了一个参数:pullSecrets,用来指定私有仓库地址的 Secrets,现在我们再去修改 templates/deployment.yaml 文件就简单很多了:
```yaml
apiVersion: apps/v1beta1
kind: Deployment
......@@ -340,7 +340,7 @@ spec:
......
```
我们首先判断是否存在值`pullSecrets`,如果存在,则将 Secrets 循环渲染出来,然后是容器的名称还是使用命名模板 wordpress.fullname 的定义,然后就是 image 的地址以及 imagePullPolicy,这样我们就完成了对镜像的定制,默认的值直接写入到 values.yaml 文件中,现在我们使用 debug 命令查看下模板渲染的结果:
我们首先判断是否存在值`pullSecrets`,如果存在,则将 Secrets 循环渲染出来,然后是容器的名称还是使用命名模板 wordpress.fullname 的定义,然后就是 image 的地址以及 imagePullPolicy,这样我们就完成了对镜像的定制,默认的值直接写入到 values.yaml 文件中,现在我们使用 debug 命令查看下模板渲染的结果:
```yaml
helm install --dry-run --debug .
[debug] Created tunnel using local port: '46735'
......@@ -382,7 +382,7 @@ spec:
......
```
假如现在我们的镜像地址是 youdianzhishi.com/wordpress:4.9,那么我们在安装的就可以覆盖 image 对象中的相关参数了:
假如现在我们的镜像地址是 youdianzhishi.com/wordpress:4.9,那么我们在安装的就可以覆盖 image 对象中的相关参数了:
```yaml
$ helm install --dry-run --debug --set image.registry=youdianzhishi.com --set image.tag=4.9 .
[debug] Created tunnel using local port: '36449'
......@@ -423,11 +423,11 @@ spec:
......
```
我们可以看到镜像地址是不是就被替换了,当然如果你需要覆盖的值比较多,最好还是通过指定一个 yaml 文件来覆盖默认的这些 values 值。
我们可以看到镜像地址是不是就被替换了,当然如果你需要覆盖的值比较多,最好还是通过指定一个 yaml 文件来覆盖默认的这些 values 值。
### 健康检查、资源限制
按照我们前面的资源文件声明,接下来我们就应该定制健康检查部分和资源限制部分,同样还是先添加模板值:(values.yaml)
按照我们前面的资源文件声明,接下来我们就应该定制健康检查部分和资源限制部分,同样还是先添加模板值:(values.yaml)
```yaml
......
## liveness 和 readliness probes 配置
......@@ -446,7 +446,7 @@ readinessProbe:
successThreshold: 1
```
我们在 values.yaml 文件中添加了 livenessProbe 和 readinessProbe 这两个对象,里面都是健康检测的相关属性,然后我们需要将这些值都嵌入到模板中去,按照以前的方法我们是不是一个属性一个属性的添加,但是这样太麻烦了,我们可以用一个函数`toYaml`将这两个对象中的属性一次性全部输出到模板中去:
我们在 values.yaml 文件中添加了 livenessProbe 和 readinessProbe 这两个对象,里面都是健康检测的相关属性,然后我们需要将这些值都嵌入到模板中去,按照以前的方法我们是不是一个属性一个属性的添加,但是这样太麻烦了,我们可以用一个函数`toYaml`将这两个对象中的属性一次性全部输出到模板中去:
```yaml
containers:
- name: {{ template "wordpress.fullname" . }}
......@@ -498,7 +498,7 @@ helm install --dry-run --debug .
......
```
可以看到符合我们的渲染结果的。然后就是我们的 resource 资源部分,因为并不是所有的应用资源消耗情况都是一样的,还需要结合自己的集群去进行定制
可以看到符合我们的渲染结果的。然后就是我们的 resource 资源部分,因为并不是所有的应用资源消耗情况都是一样的,还需要结合自己的集群去进行定制
```yaml
## Configure resource requests and limits
......
......@@ -311,7 +311,7 @@ log_queries =
enabled = true
host = smtp.163.com:25
user = ych_1024@163.com
password = OTFych88198900
password = <邮箱密码>
skip_verify = true
from_address = ych_1024@163.com
;enabled = false
......
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: dingtalk-hook
namespace: kube-ops
spec:
template:
metadata:
labels:
app: dingtalk-hook
spec:
containers:
- name: dingtalk-hook
image: cnych/alertmanager-dingtalk-hook:v0.1.3
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5000
name: http
env:
- name: ROBOT_TOKEN
valueFrom:
secretKeyRef:
name: dingtalk-secret
key: token
resources:
requests:
cpu: 50m
memory: 100Mi
limits:
cpu: 50m
memory: 100Mi
---
apiVersion: v1
kind: Service
metadata:
name: dingtalk-hook
namespace: kube-ops
spec:
selector:
app: dingtalk-hook
ports:
- name: hook
port: 5000
targetPort: http
......@@ -40,6 +40,25 @@ spec:
limits:
cpu: 100m
memory: 512Mi
- name: alertmanager
image: prom/alertmanager:v0.15.3
imagePullPolicy: IfNotPresent
args:
- "--config.file=/etc/alertmanager/config.yml"
- "--storage.path=/alertmanager/data"
ports:
- containerPort: 9093
name: http
volumeMounts:
- mountPath: "/etc/alertmanager"
name: alertcfg
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 100m
memory: 256Mi
volumes:
- name: data
persistentVolumeClaim:
......@@ -47,5 +66,8 @@ spec:
- name: config
configMap:
name: prometheus-config
- name: alertcfg
configMap:
name: alert-config
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册