Jim

Talk is cheap. Show me the code.


  • 首页

  • 归档

  • 分类

  • 标签

  • 关于

  • 搜索

通过 swagger-ui 查看 kube-apiserver apis

发表于 2020-07-11 | 分类于 Kubernetes

从 k8s v1.14 开始,官方已经废弃了 swagger 接口,使用 openapi 规范,暴露出来的接口是: /openapi/v2,我们要想通过
swagger-ui 来查看 apiserver 接口,可以自己本地跑个 swagger-ui 服务,然后访问 kube-apiserver 地址的 openapi 接口地址即可,swagger-ui 来源支持 openapi 数据格式。

1.本地启动 swagger-ui 服务

1
docker run --name swagger-ui --restart always -p 7070:8080 -d swaggerapi/swagger-ui:latest

2.访问 swagger-ui 地址 http://127.0.0.1:7070,输入 kube-apiserver 地址的 openapi 接口地址:http://kube-apiserver-ip:8080/openapi/v2

这里 openapi 地址是 kube-apiserver 的 http 地址,为了方便,如果是 https 比较麻烦,因为涉及到了 apiserver 的认证授权相关东西。所以为了方便本地调试,还是用 http 地址。
注意:直接访问存在跨域问题,需要安装 Chrome 的 Allow CORS 插件实现跨域访问,插件传送门:https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf?hl=en

参考文档:
https://kubernetes.io/zh/docs/concepts/overview/kubernetes-api/
https://blog.schwarzeni.com/2019/09/16/Minikube%E4%BD%BF%E7%94%A8Swagger%E6%9F%A5%E7%9C%8BAPI/

Golang 项目中集成日志功能

发表于 2020-07-11 | 分类于 Go

在一个 web 项目中,日志打印功能是必须的,有了详细的日志能为问题排查带来很大的便利。Golang 有很多开源的日志包可供使用,这里我还是使用非常流行的 logrus 包,结合 file-rotatelogs 包实现日志的自动切割轮转。

集成方法:

  • 1.单独定义一个 package 名为 logger,里面只有一个 init.go 文件,初始化日志配置,这个配置是全局的:

    • 日志输出格式为 json;
    • 日志自动轮转,保留最近 7 天日志,一天产生一个日志文件,防止服务长时间运行占用系统过多的磁盘资源;
    • 日志在输出到文件的同时并打到 stdout;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      package logger

      import (
      "io"
      "os"
      "path"
      "time"

      log "github.com/sirupsen/logrus"
      rotatelogs "github.com/lestrrat-go/file-rotatelogs"

      "github.com/demo/pkg/common/config"
      "github.com/demo/pkg/utils/fs"
      )

      // 全局日志配置初始化
      // 1.日志同时输出到到文件和标准输出;
      // 2.保留最近 7 天日志,一天一个日志文件;
      //
      func init() {
      logDir := config.GetConfig().GetString("log_dir")
      if !fs.PathExists(logDir) {
      if err := os.MkdirAll(logDir, 0755); err != nil {
      panic(err)
      }
      }

      logfile := path.Join(logDir, "app")
      fsWriter, err := rotatelogs.New(
      logfile+"_%Y-%m-%d.log",
      rotatelogs.WithMaxAge(time.Duration(168)*time.Hour),
      rotatelogs.WithRotationTime(time.Duration(24)*time.Hour),
      )
      if err != nil {
      panic(err)
      }

      multiWriter := io.MultiWriter(fsWriter, os.Stdout)
      log.SetReportCaller(true)
      log.SetFormatter(&log.JSONFormatter{})
      log.SetOutput(multiWriter)
      log.SetLevel(log.InfoLevel)
      }
  • 2.在项目入口 main 包引入该包,初始化全局日志配置

    1
    2
    3
    4
    package main
    import (
    _ "github.com/demo/pkg/logger" // 引入包时自动执行了 logger 包的 init 函数完成了日志初始化配置
    )
  • 3.至此,在项目其他地方如果要打印日志,直接引用 logrus 包即可,在 main 包中已经完成了日志配置,无需在其他使用的地方再次初始化配置;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    package hello

    import (
    log "github.com/sirupsen/logrus"
    )

    func HelloLog() {
    log.Info("HelloLog func called")
    }

Golang 从 Json 串中快速取出需要的字段

发表于 2020-07-11 | 分类于 Go

在 web 编程中很多情况下接口的数据是 json 格式,在我们拿到接口的 json 数据后如何方便地从中提取出需要的字段呢?我们可以自定义一个结构体,然后通过 Golang 的标准库 json 解析到我们定义的结构体中。但是当 json 格式比较复杂,嵌套层级比较深的时候,还是用这种方法就比较麻烦了。在这里推荐一个包: gojsonq,可以很简便地从 json 串中提取出需要的字段,无需定义额外的结构体,然后解析,直接链式地从 json 串中提取需要的字段即可,有点类似动态语言:

1
go get github.com/thedevsaddam/gojsonq/v2

  • Github 地址:
    https://github.com/thedevsaddam/gojsonq

  • Package 简介:
    A simple Go package to Query over JSON Data. It provides simple, elegant and fast ODM like API to access, query JSON document.

示例一:从如下 json 串中提取 name.first 字段数据

1
2
3
4
5
6
7
{
"name":{
"first":"Tom",
"last":"Hanks"
},
"age":61
}

Find 方法返回的结果为 interface{} 类型。

1
2
3
4
5
6
7
8
9
package main

import gojsonq "github.com/thedevsaddam/gojsonq/v2"

func main() {
const json = `{"name":{"first":"Tom","last":"Hanks"},"age":61}`
name := gojsonq.New().FromString(json).Find("name.first")
println(name.(string)) // Tom
}

示例二:从如下 json 串中提取 temperatures 字段数据,并计算平均值

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"city":"dhaka",
"type":"weekly",
"temperatures":[
30,
39.9,
35.4,
33.5,
31.6,
33.2,
30.7
]
}

支持列表计算:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"

gojsonq "github.com/thedevsaddam/gojsonq/v2"
)

func main() {
const json = `{"city":"dhaka","type":"weekly","temperatures":[30,39.9,35.4,33.5,31.6,33.2,30.7]}`
avg := gojsonq.New().FromString(json).From("temperatures").Avg()
fmt.Printf("Average temperature: %.2f", avg) // 33.471428571428575
}

示例三:从如下 json 串中提取 data.pageNumber 数组,赋值给 Golang 整型数组

上面的例子提取简单的数值类型,返回的是 interface{} 类型,可以通过类型断言得到需要的数据。如果提取的是数组类型,是无法通过类型断言来获取 interface{} 类型的值的,可以使用 FindR 拿到结果,然后通过结果的 As 方法将数据解析到我们定义的变量类型中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"code":"0",
"message":"success",
"data":{
"thumb_up":0,
"system":0,
"im":0,
"avatarUrl":"https://profile.csdnimg.cn/F/4/B/helloworld",
"invitation":0,
"comment":0,
"follow":0,
"totalCount":0,
"coupon_order":0,
"pageNumbers":[
1,
2,
3,
4,
5,
6
]
},
"status":true
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
func main() {
jsonStr := `{
"code":"0",
"message":"success",
"data":{
"thumb_up":0,
"system":0,
"im":0,
"avatarUrl":"https://profile.csdnimg.cn/F/4/B/helloworld",
"invitation":0,
"comment":0,
"follow":0,
"totalCount":0,
"coupon_order":0,
"pageNumbers":[
1,
2,
3,
4,
5,
6
]
},
"status":true
}`

// 保存 json 字符串中 pageNumbers 字段列表数据
pageNumbers := make([]int, 0)
result, err := gojsonq.New().FromString(jsonStr).FindR("data.pageNumbers")
if err != nil {
fmt.Println(err)
}
// 将提前的数据解析为自定义变量类型
err = result.As(&pageNumbers)
if err != nil {
fmt.Println(err)
}
fmt.Println(pageNumbers)
}

更多功能及示例见官方文档:
https://github.com/thedevsaddam/gojsonq/wiki/Queries

kubernetes 源码编译

发表于 2020-03-01 | 分类于 Kubernetes

kubernetes 源码编译分为本地编译和镜像编译,本地编译是指最终编译出来的是二进制可执行文件,镜像编译是
最终编译出来的产出物为 docker 镜像 tar 包。本文主要介绍本地编译的方法,以编译 kube-apiserver 组件为例说明。

环境要求

  • Go 环境: go1.12.xx
  • gcc

我的环境说明:Mac Os + go1.12.10 + gcc,如果读者本地的 Go 版本不是 go1.12.xx,可以使用 gvm 工具安装一个,gvm 是 Go 多版本管理工具,具体使用方法可以看之前的文章:Golang 多版本管理神器 gvm

本地编译也分为两种,一种是 make 编译,另一种是 Go 命令行编译,下面一一介绍:

一、make 编译

1.下载 k8s 源码

1
go get k8s.io/kubernetes

2.编译指定版本源码(以1.16.3为例)

1
2
cd $GOPATH/src/k8s.io/kubernetes
git checkout tags/v1.16.3

3.设置要编译的组件(以编译 kube-apiserver 组件为例说明)

mac 下编译要安装 GNU tar: sudo brew install gnu-tar

1
make clean && make WHAT=cmd/kube-apiserver

编译产出物会在 _output/bin 目录生成:

1
2
3
4
5
6
7
8
$ ls -1 _output/bin
conversion-gen
deepcopy-gen
defaulter-gen
go-bindata
go2make
kube-apiserver
openapi-gen

二、Go 命令编译

1.cd $GOPATH/src/k8s.io/kubernetes && make generated_files
2.进入对应组件目录编译(以 kube-apiserver组件编译为例)

1
cd cmd/kube-apiserver && go build -v

源码编译可能遇到的问题

编译可能报类似下面错误:

1
2
3
4
5
6
7
8
go/build: importGo k8s.io/kubernetes: exit status 1
can't load package: package k8s.io/kubernetes: cannot find module providing package k8s.io/kubernetes

+++ [0301 18:04:57] Building go targets for darwin/amd64:
./vendor/k8s.io/code-generator/cmd/deepcopy-gen
can't load package: package k8s.io/kubernetes/vendor/k8s.io/code-generator/cmd/deepcopy-gen: cannot find module providing package k8s.io/kubernetes/vendor/k8s.io/code-generator/cmd/deepcopy-gen
!!! [0301 18:05:04] Call tree:
!!! [0301 18:05:04] 1:

解决办法:golang 版本换成 go1.12.xx 即可。
具体 issue 见这里:https://github.com/kubernetes/kubernetes/issues/84224

参考资料

https://www.kubernetes.org.cn/5033.html
https://blog.csdn.net/boling_cavalry/article/details/88591982
https://github.com/MicrosoftDocs/Virtualization-Documentation/blob/master/virtualization/windowscontainers/kubernetes/compiling-kubernetes-binaries.md

Golang 多版本管理神器 gvm

发表于 2020-03-01 | 分类于 Go

缘起

最近编译 kubernetes 遇到了点坑,编译各种报错,经搜索调研发现 k8s 的编译对 go 的版本有很严格的要求。比如我的 go1.13.4 就无法编译 kubernetes v1.16.3,必须得 go1.12.xx 版本才能编译。为了解决这种尴尬的场景只能再在主机安装个 go1.12.xx 版本,那么有没有什么优雅的方式来实现本机多版本 Golang 版本的管理呢,能很方便的进行不同版本的切换,这也是本文的目的,推荐一款 Go多版本管理神器 gvm,用法类似 Python 的多版本管理工具 pyenv。

简介

gvm,即 Go Version Manager,Go 版本管理器,使用 shell 脚本开发,它可以非常轻量的切换 Go 版本。对比其他语言,通常也有类似的工具,如 NodeJS 的 NVM,Python 的 pyenv 等。在使用方法上和 Python 的多版本管理工具 pyenv 非常类似。

其实不借助类似的版本管理工具安装多个版本 Go 也是可以自己手动实现的,做法很简单,就是下载不同的 Golang 安装包,然后放置到独立的目录,使用时将 GOROOT 和 GOPATH 指向对应版本的目录即可完成版本切换。其实 gvm 原理上就是这么做的,只不过通过工具的形式将这些繁杂的手工操作封装起来,使得开发起来更加优雅,不必再为 Go 的安装、版本管理花费更多的心思。下面为 gvm 的工作原理:

gvm

安装

1
bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer)

安装可能遇到的坑:

1
2
3
$ gvm  install go1.12.10
Downloading Go source...
ERROR: Couldn't download Go source. Check the logs /Users/jim/.gvm/logs/go-download.log

根据提示看 log 报错

1
2
3
4
5
Cloning into '/Users/jim/.gvm/archive/go'...
error: RPC failed; curl 18 transfer closed with outstanding read data remaining
fatal: the remote end hung up unexpectedly
fatal: early EOF
fatal: index-pack failed

解决问题:vim ~/.gvm/scripts/install
修改 GO_SOURCE_URL 变量地址为: GO_SOURCE_URL=git://github.com/golang/go

使用速记

1.列出当前已安装的 Go 版本

1
gvm list

2.列出当前可以安装的 Go 版本

1
gvm listall

3.安装指定版本的 Go

1
gvm install go1.12.10

4.切换到指定的 Go 版本
临时切换

1
gvm use go1.12

永久切换

1
gvm use go1.12 --default

参考资料

https://juejin.im/post/5d848b66f265da03a7160e89

Golang logrus 日志包及日志切割

发表于 2020-03-01 | 分类于 Go

本文主要介绍 Golang 中最佳日志解决方案,包括常用日志包 logrus 的基本使用,如何结合 file-rotatelogs 包实现日志文件的轮转切割两大话题。

Golang 关于日志处理有很多包可以使用,标准库提供的 log 包功能比较少,不支持日志级别的精确控制,自定义添加日志字段等。在众多的日志包中,更推荐使用第三方的 logrus 包,完全兼容自带的 log 包。logrus 是目前 Github 上 star 数量最多的日志库,logrus 功能强大,性能高效,而且具有高度灵活性,提供了自定义插件的功能。

很多开源项目,如 docker,prometheus,dejavuzhou/ginbro 等,都是用了 logrus 来记录其日志。

logrus 特性

  • 完全兼容 golang 标准库日志模块:logrus 拥有六种日志级别:debug、info、warn、error、fatal 和 panic,这是 golang 标准库日志模块的 API 的超集。
    • logrus.Debug(“Useful debugging information.”)
    • logrus.Info(“Something noteworthy happened!”)
    • logrus.Warn(“You should probably take a look at this.”)
    • logrus.Error(“Something failed but I’m not quitting.”)
    • logrus.Fatal(“Bye.”) //log之后会调用os.Exit(1)
    • logrus.Panic(“I’m bailing.”) //log之后会panic()
  • 可扩展的 Hook 机制:允许使用者通过 hook 的方式将日志分发到任意地方,如本地文件系统、标准输出、logstash、elasticsearch 或者 mq 等,或者通过 hook 定义日志内容和格式等。
  • 可选的日志输出格式:logrus 内置了两种日志格式,JSONFormatter 和 TextFormatter,如果这两个格式不满足需求,可以自己动手实现接口 Formatter 接口来定义自己的日志格式。
  • Field 机制:logrus 鼓励通过 Field 机制进行精细化的、结构化的日志记录,而不是通过冗长的消息来记录日志。
  • logrus 是一个可插拔的、结构化的日志框架。
  • Entry: logrus.WithFields 会自动返回一个 *Entry,Entry里面的有些变量会被自动加上
    • time:entry被创建时的时间戳
    • msg:在调用.Info()等方法时被添加
    • level,当前日志级别

logrus 基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
"os"

"github.com/sirupsen/logrus"
log "github.com/sirupsen/logrus"
)

var logger *logrus.Entry

func init() {
// 设置日志格式为json格式
log.SetFormatter(&log.JSONFormatter{})
log.SetOutput(os.Stdout)
log.SetLevel(log.InfoLevel)
logger = log.WithFields(log.Fields{"request_id": "123444", "user_ip": "127.0.0.1"})
}

func main() {
logger.Info("hello, logrus....")
logger.Info("hello, logrus1....")
// log.WithFields(log.Fields{
// "animal": "walrus",
// "size": 10,
// }).Info("A group of walrus emerges from the ocean")

// log.WithFields(log.Fields{
// "omg": true,
// "number": 122,
// }).Warn("The group's number increased tremendously!")

// log.WithFields(log.Fields{
// "omg": true,
// "number": 100,
// }).Fatal("The ice breaks!")
}

基于 logrus 和 file-rotatelogs 包实现日志切割

很多时候应用会将日志输出到文件系统,对于访问量大的应用来说日志的自动轮转切割管理是个很重要的问题,如果应用不能妥善处理日志管理,那么会带来很多不必要的维护开销:外部工具切割日志、人工清理日志等手段确保不会将磁盘打满。

file-rotatelogs: When you integrate this to to you app, it automatically write to logs that are rotated from within the app: No more disk-full alerts because you forgot to setup logrotate!

logrus 本身不支持日志轮转切割功能,需要配合 file-rotatelogs 包来实现,防止日志打满磁盘。file-rotatelogs 实现了 io.Writer 接口,并且提供了文件的切割功能,其实例可以作为 logrus 的目标输出,两者能无缝集成,这也是 file-rotatelogs 的设计初衷:

It’s normally expected that this library is used with some other logging service, such as the built-in log library, or loggers such as github.com/lestrrat-go/apache-logformat.

示例代码:
应用日志文件 /Users/opensource/test/go.log,每隔 1 分钟轮转一个新文件,保留最近 3 分钟的日志文件,多余的自动清理掉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
"time"

rotatelogs "github.com/lestrrat-go/file-rotatelogs"
log "github.com/sirupsen/logrus"
)

func init() {
path := "/Users/opensource/test/go.log"
/* 日志轮转相关函数
`WithLinkName` 为最新的日志建立软连接
`WithRotationTime` 设置日志分割的时间,隔多久分割一次
WithMaxAge 和 WithRotationCount二者只能设置一个
`WithMaxAge` 设置文件清理前的最长保存时间
`WithRotationCount` 设置文件清理前最多保存的个数
*/
// 下面配置日志每隔 1 分钟轮转一个新文件,保留最近 3 分钟的日志文件,多余的自动清理掉。
writer, _ := rotatelogs.New(
path+".%Y%m%d%H%M",
rotatelogs.WithLinkName(path),
rotatelogs.WithMaxAge(time.Duration(180)*time.Second),
rotatelogs.WithRotationTime(time.Duration(60)*time.Second),
)
log.SetOutput(writer)
//log.SetFormatter(&log.JSONFormatter{})
}

func main() {
for {
log.Info("hello, world!")
time.Sleep(time.Duration(2) * time.Second)
}
}

Golang 标准日志库 log 使用

虽然 Golang 标准日志库功能少,但是可以选择性的了解下,下面为基本使用的代码示例,比较简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"log"
)

func init() {
log.SetPrefix("【UserCenter】") // 设置每行日志的前缀
log.SetFlags(log.LstdFlags | log.Lshortfile | log.LUTC) // 设置日志的抬头字段
}

func main() {
log.Println("log...")
log.Fatalln("Fatal Error...")
fmt.Println("Not print!")
}

自定义日志输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"io"
"log"
"os"
)

var (
Info *log.Logger
Warning *log.Logger
Error *log.Logger
)

func init() {
errFile, err := os.OpenFile("errors.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatalln("打开日志文件失败:", err)
}

Info = log.New(os.Stdout, "Info:", log.Ldate|log.Ltime|log.Lshortfile)
Warning = log.New(os.Stdout, "Warning:", log.Ldate|log.Ltime|log.Lshortfile)
Error = log.New(io.MultiWriter(os.Stderr, errFile), "Error:", log.Ldate|log.Ltime|log.Lshortfile)
}

func main() {
Info.Println("Info log...")
Warning.Printf("Warning log...")
Error.Println("Error log...")
}

相关文档

https://mojotv.cn/2018/12/27/golang-logrus-tutorial
https://github.com/lestrrat-go/file-rotatelogs
https://www.flysnow.org/2017/05/06/go-in-action-go-log.html

使用 Prometheus Operator 构建 Kubernetes 监控系统

发表于 2019-12-15 | 分类于 Kubernetes

本文主要介绍使用 Prometheus 监控 Kubernetes 的最佳实践,借助 Prometheus Operator 和 Helm 快速完成 Kubernetes 集群的监控。

Prometheus Operator 是 SRE 的一种实践,一种新的软件类型,大大简化了在 Kubernetes 上部署、管理和运行 Prometheus 和 Alertmanager 集群,同时还保持 Kubernetes 原生配置项,和 Kubernetes 无缝贴合。

什么是 Prometheus?

Prometheus 是由 SoundCloud 开源的监控告警解决方案,从 2012 年开始编写代码,再到 2015 年 github 上开源以来,已经吸引了 9k+ 关注,以及很多大公司的使用;2016 年 Prometheus 成为继 k8s 后,第二名 CNCF(Cloud Native Computing Foundation) 成员。

作为新一代开源解决方案,很多理念与 Google SRE 运维之道不谋而合。比如 Operator 的概念:将复杂运维工作固化到 Operator 软件实体中。

Prometheus 主要功能

  • 多维 数据模型(时序由 metric 名字和 k/v 的 labels 构成)。
  • 灵活的查询语句(PromQL)。
  • 无依赖存储,支持 local 和 remote 不同模型。
  • 采用 http 协议,使用 pull 模式,拉取数据,简单易懂。
  • 监控目标,可以采用服务发现或静态配置的方式。
  • 支持多种统计数据模型,图形化友好。

Prometheus 核心组件

  • the main Prometheus server which scrapes and stores time series data
  • client libraries for instrumenting application code
  • a push gateway for supporting short-lived jobs
  • special-purpose exporters for services like HAProxy, StatsD, Graphite, etc.
  • an alertmanager to handle alerts
  • various support tools

Prometheus 架构

Alt text

从架构图也可以看出 Prometheus 的主要模块包含,Server, Exporters, Pushgateway, PromQL, Alertmanager, WebUI 等。
各组件之间的协同逻辑:

  1. Prometheus server 定期从静态配置的 targets 或者服务发现的 targets 拉取数据。
  2. 当新拉取的数据大于配置内存缓存区的时候,Prometheus 会将数据持久化到磁盘(如果使用 remote storage 将持久化到云端)。
  3. Prometheus 可以配置 rules,然后定时查询数据,当条件触发的时候,会将 alert 推送到配置的 Alertmanager。
  4. Alertmanager 收到警告的时候,可以根据配置,聚合,去重,降噪,最后发送警告。
  5. 可以使用 API,Prometheus Console 或者 Grafana 查询和聚合数据。

什么是 Operator?

Operator 是 CoreOs 提出的一个概念,也是 SRE 的一种实践,通过开发软件的方式将复杂运维工作固化到 Operator 中。
Operator 是一种新的基于 Kubernetes 平台的软件类型,主要解决 Kubernetes 中某些复杂应用的运维部书工作,利用 Kubernetes 的 Resources 和 Controllers 两种资源对象实现 Operator,Resources 描述期望的目标状态,Controllers 负责达成期望目标的具体实现逻辑。
Alt text

Prometheus Operator 介绍

Prometheus 是一套开源的系统监控、报警、时间序列数据库的组合,而 Prometheus Operator 是 CoreOS 开源的一套用于管理在 Kubernetes 集群上的 Prometheus 控制器,它是为了简化在 Kubernetes 上部署、管理和运行 Prometheus 和 Alertmanager 集群,同时还保持 Kubernetes 原生配置项,和 Kubernetes 无缝贴合。
Alt text

  • Operator: Operator 资源会根据自定义资源(Custom Resource Definition / CRDs)来部署和管理 Prometheus Server,同时监控这些自定义资源事件的变化来做相应的处理,是整个系统的控制中心。
  • Prometheus: Prometheus 资源是声明性地描述 Prometheus 部署的期望状态。
  • Prometheus Server: Operator 根据自定义资源 Prometheus 类型中定义的内容而部署的 Prometheus Server 集群,这些自定义资源可以看作是用来管理 Prometheus Server 集群的 StatefulSets 资源。
  • ServiceMonitor: ServiceMonitor 也是一个自定义资源,它描述了一组被 Prometheus 监控的 targets 列表。该资源通过 Labels 来选取对应的 Service Endpoint,让 Prometheus Server 通过选取的 Service 来获取 Metrics 信息。
  • Service: kubernetes 的 service 资源,提供 metrics 接口,供 Prometheus 抓取。
  • Alertmanager: Alertmanager 也是一个自定义资源类型,由 Operator 根据资源描述内容来部署 Alertmanager 集群。

Helm 部署 Prometheus Operator

使用 GitHub 提供的 prometheus-operator Chart 部署,该 Chart 提供了一套完整的 Prometheus 监控栈,不需要其他任何安装部署操作,一个 Chart 3 分钟搞定一切工作。

1
helm install --name kube-prometheus stable/prometheus-operator --namespace monitoring

测试访问:
Grafana UI

1
kubectl port-forward <grafna pod> --address 0.0.0.0 -n monitoring 3000:3000

Alt text

Prometheus UI

1
kubectl port-forward <prometheus pod> --address 0.0.0.0 -n monitoring 9090:9090

Alt text

参考文档

https://coreos.com/blog/the-prometheus-operator.html
https://github.com/coreos/kube-prometheus
https://github.com/coreos/prometheus-operator
https://blog.csdn.net/aixiaoyang168/article/details/81661459
https://blog.51cto.com/3241766/2450776
https://www.chenshaowen.com/blog/quickly-deploy-prometheus-using-helm-and-operator.html

MySQL 慢查询日志导入 Elasticsearch 可视化查询分析

发表于 2019-10-24 | 分类于 MySQL

当应用程序后台 SQL 查询慢的时候我们一般第一时间会查看数据库慢查询记录,但是慢查询记录是原始文本,直接查询搜索分析比较费时费力,虽然业界有针对 MySQL 慢查询分析的命令行工具(比如:pt-query-digest),但是使用起来还是不够方便,而且分析结果也是针对整个实例的大概统计,不能及时定位到某个应用(库.表)的慢查询。出于这个目的我们可以将 MySQL 原始慢查询日志结构化导入 Elasticsearch,然后通过 Kibana 可视化查询分析,由于日志结构化解析出来了,所以可以快速查询分析。本文主要介绍如何运用业界主流的开源工具链实现这一需求,整体架构如下:

工具链集合

  1. Filebeat:日志收集端,使用 Filebeat 的 MySQL 模块结构化解析慢查询日志并写入到 Elasticsearch。
  2. Elasticsearch:存储 Filebeat 发送过来的日志消息;
  3. Kibana:可视化查询分析 Elasticsearch 存储的日志数据;
  4. docker-compose:容器化快速启动 Elasticsearch + Kibana 组件;

具体实现

docker-compose 启动 Elasticsearch + Kibana 组件,然后使用 Filebeat 自带的 MySQL 模块结构化慢查询日志并传输到 Elasticsearch。

docker-compose 启动 Elasticsearch + Kibana 组件

  • Elasticsearch 7.4.0
  • Kibana 7.4.0

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
version: '3'

networks:
esnet:
driver: bridge
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.4.0
environment:
- node.name=es01
- discovery.seed_hosts=es01
- cluster.initial_master_nodes=es01
- cluster.name=docker-cluster
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms4096m -Xmx4096m"
ulimits:
memlock:
soft: -1
hard: -1
ports:
- "9500:9200"
networks:
- esnet
kibana:
image: docker.elastic.co/kibana/kibana:7.4.0
environment:
- "ELASTICSEARCH_HOSTS=http://elasticsearch:9200"
ports:
- "5601:5601"
networks:
- esnet

启动:

1
docker-compose up -d

安装配置 Filebeat

安装
1
2
curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.4.0-x86_64.rpm
sudo rpm -vi filebeat-7.4.0-x86_64.rpm
配置 Filebeat
  1. 配置 Filebeat 输出到 Elasticsearch:
    vim /etc/filebeat/filebeat.yml 填入如下内容

    1
    2
    3
    output.elasticsearch:
    # Array of hosts to connect to.
    hosts: ["localhost:9500"]
  2. 激活 filebeat mysql 模块

    1
    filebeat modules enable mysql

    关于 Filebeat mysql 模块介绍见这里:https://www.elastic.co/guide/en/beats/filebeat/master/filebeat-module-mysql.html

  3. 设置初始化环境

    1
    filebeat setup -e
  4. 慢查询日志抓取目录路径
    这里设置为:/data1/web/slow-query/original/*.log 路径
    vim /etc/filebeat/modules.d/mysql.yml

    1
    2
    3
    4
    5
    6
    - module: mysql
    error:
    enabled: false
    slowlog:
    enabled: true
    var.paths: ["/data1/web/slow-query/original/*.log"]
  5. Filebeat MySQL 慢查询日志解析配置
    由于我们 MySQL 使用云提供商的 SQL 服务,但是云提供商的 MySQL 实例慢查询日志格式和自搭的有略微的区别,不太是很标准,所以需要自定义日志格式解析表达式,正则表达式符合 logstash Grok 语法,可以在这里调试正则表达式:http://grokdebug.herokuapp.com/。关于 Grok 正则语法的学习资料可以看看这两篇文章,这里不做介绍:ELK实战 - Grok简易入门,Logstash 官网:Grok 过滤器插件。

    我们目前慢查询日志格式样例:

    1
    2
    3
    4
    5
    6
    # Time: 2019-10-23T00:00:22.964315Z
    # User@Host: db[db] @ [cloudsqlproxy~192.168.1.11] thread_id: 87983 server_id: 2945557302
    # Query_time: 1.649439 Lock_time: 0.000116 Rows_sent: 1 Rows_examined: 1634
    use report;
    SET timestamp=1571788822;
    select * from table where team_id = 71206683786887168 and definition_id = 142 and definition_md5 = 'acd2e0a2fecb08ceb13c6ae' and UNIX_TIMESTAMP(create_time) * 1000 <= 1568851199999 order by create_time desc limit 1;

    对应的 Grok 正则:
    说明:在 Grok 中转义一个字符使用一个 \ 而不是两个 \,比如要转义 [ 需要写成 \[。

    1
    ^# Time: %{TIMESTAMP_ISO8601:mysql.slowlog.time}\n# User@Host: %{USER:mysql.slowlog.user}\[%{USER:mysql.slowlog.current_user}\] @ %{HOSTNAME:mysql.slowlog.host}? \[([a-zA-Z~]*)?%{IP:mysql.slowlog.ip}?\]%{SPACE}(Id:%{SPACE}%{NUMBER:mysql.slowlog.id:int})?(%{SPACE}thread_id:%{SPACE}%{NUMBER:mysql.slowlog.thread_id:int})?(%{SPACE}server_id:%{SPACE}%{NUMBER:mysql.slowlog.server_id})?\n# Query_time: %{NUMBER:mysql.slowlog.query_time.sec:float}%{SPACE}Lock_time: %{NUMBER:mysql.slowlog.lock_time.sec:float}%{SPACE}Rows_sent: %{NUMBER:mysql.slowlog.rows_sent:int}%{SPACE}Rows_examined: %{NUMBER:mysql.slowlog.rows_examined:float}\n((use|USE) .*;\n)?SET timestamp=%{NUMBER:mysql.slowlog.timestamp};\n%{GREEDYDATA:mysql.slowlog.query}

    将调试好的 Grok 正则写入下面文件中:
    说明: 写入下面 pipeline.json 文件中的正则特殊字符需要两个 \ 转义,比如要转义 [ 需要写成 \\[。
    /usr/share/filebeat/module/mysql/slowlog/ingest/pipeline.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    {
    "description": "Pipeline for parsing MySQL slow logs.",
    "processors": [{
    "grok": {
    "field": "message",
    "patterns":[
    "^# Time: %{TIMESTAMP_ISO8601:mysql.slowlog.time}\n# User@Host: %{USER:mysql.slowlog.user}\\[%{USER:mysql.slowlog.current_user}\\] @ %{HOSTNAME:mysql.slowlog.host}? \\[([a-zA-Z~]*)?%{IP:mysql.slowlog.ip}?\\]%{SPACE}(Id:%{SPACE}%{NUMBER:mysql.slowlog.id:int})?(%{SPACE}thread_id:%{SPACE}%{NUMBER:mysql.slowlog.thread_id:int})?(%{SPACE}server_id:%{SPACE}%{NUMBER:mysql.slowlog.server_id})?\n# Query_time: %{NUMBER:mysql.slowlog.query_time.sec:float}%{SPACE}Lock_time: %{NUMBER:mysql.slowlog.lock_time.sec:float}%{SPACE}Rows_sent: %{NUMBER:mysql.slowlog.rows_sent:int}%{SPACE}Rows_examined: %{NUMBER:mysql.slowlog.rows_examined:float}\n((use|USE) .*;\n)?SET timestamp=%{NUMBER:mysql.slowlog.timestamp};\n%{GREEDYDATA:mysql.slowlog.query}"
    ],
    "pattern_definitions" : {
    "GREEDYMULTILINE" : "(.|\n)*"
    },
    "ignore_missing": false
    }
    }, {
    "remove":{
    "field": "message"
    }
    }, {
    "date": {
    "field": "mysql.slowlog.time",
    "target_field": "@timestamp",
    "formats": ["ISO8601"],
    "ignore_failure": true
    }
    }],
    "on_failure" : [{
    "set" : {
    "field" : "error.message",
    "value" : "{{ _ingest.on_failure_message }}"
    }
    }]
    }

    /usr/share/filebeat/module/mysql/slowlog/config/slowlog.yml 文件调整如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    type: log
    paths:
    {{ range $i, $path := .paths }}
    - {{$path}}
    {{ end }}
    exclude_files: ['.gz$']
    multiline:
    pattern: '^# Time:'
    negate: true
    match: after
    exclude_lines: ['^[\/\w\.]+, Version: .* started with:.*'] # Exclude the header

    启动 filebeat 开始日志收集:

    1
    systemctl start filebeat

Kibana 可视化查询

在 Kibana 界面创建 filebeat 索引即可实时查看慢查询日志,举例:查看最近 7 天 10s ~ 20s 的慢查询记录:

遇到的问题及解决方法

Kibana 首次查看上面建立的索引数据可能会报类似下面的错误,主要原因是单条日志事件过长 Kibana 有限制:

1
Request to Elasticsearch failed: {"error":{"root_cause":[{"type":"illegal_argument_exception","reason":"The length of [error.message] field of [JHzl020BgPENHIlNYRoc] doc of [filebeat-7.4.0-2019.10.16-000001] index has exceeded [1000000] - maximum allowed to be analyzed for highlighting. This maximum can be set by changing the [index.highlight.max_analyzed_offset] index level setting. For large texts, indexing with offsets or term vectors is recommended!"}],"type":"search_phase_execution_exception","reason":"all shards failed","phase":"query","grouped":true,"failed_shards":[{"shard":0,"index":"filebeat-7.4.0-2019.10.16-000001","node":"H_Zq22spSJKz0RWR_alDsA","reason":{"type":"illegal_argument_exception","reason":"The length of [error.message] field of [JHzl020BgPENHIlNYRoc] doc of [filebeat-7.4.0-2019.10.16-000001] index has exceeded [1000000] - maximum allowed to be analyzed for highlighting. This maximum can be set by changing the [index.highlight.max_analyzed_offset] index level setting. For large texts, indexing with offsets or term vectors is recommended!"}}],"caused_by":{"type":"illegal_argument_exception","reason":"The length of [error.message] field of [JHzl020BgPENHIlNYRoc] doc of [filebeat-7.4.0-2019.10.16-000001] index has exceeded [1000000] - maximum allowed to be analyzed for highlighting. This maximum can be set by changing the [index.highlight.max_analyzed_offset] index level setting. For large texts, indexing with offsets or term vectors is recommended!","caused_by":{"type":"illegal_argument_exception","reason":"The length of [error.message] field of [JHzl020BgPENHIlNYRoc] doc of [filebeat-7.4.0-2019.10.16-000001] index has exceeded [1000000] - maximum allowed to be analyzed for highlighting. This maximum can be set by changing the [index.highlight.max_analyzed_offset] index level setting. For large texts, indexing with offsets or term vectors is recommended!"}}},"status":400}

解决方法:
打开 Kibana –> Management –> Advanced Settings –> Highlight results 开关关闭。

关于 Filebeat 的一些使用心得

1. yum 方式安装的 Filebeat 其日志如何查看?

  1. journalctl -u filebeat -f
  2. tail -f /var/log/filebeat/filebeat

2. Filebeat 状态清理:清理 registry
有时候我们需要清理下 Filebeat 状态,从头开始读取日志,yum 方式安装的 filebeat 直接清理 /var/lib/filebeat/registry 文件夹即可。

参考文档

https://xiezefan.me/2017/04/09/elk_in_action_grok_start/ | Grok 简易入门
https://github.com/elastic/logstash/blob/v1.4.2/patterns/grok-patterns | Grok 本身支持的模式列表
https://churrops.io/2018/06/18/elastic-modulo-mysql-do-filebeat-para-capturar-slowlogs-slow-queries/
https://discuss.elastic.co/t/filebeat-mysql-module-slowlog-error-message-provided-grok-expressions-do-not-match-field-value/135945

基于 Kubernetes 的 7 大 DevOps 关键实践

发表于 2019-09-07 | 分类于 DevOps

本文主要介绍 DevOps 的 7 大关键实践在 Kubernetes 平台下如何落地,结合我们目前基于 Kubernetes 平台的 DevOps 实践谈谈是如何贯彻相关理念的,这里不会对其具体实现细节进行深入讲解,只做一个大致的概括的描述,分享下已有的实践,具体实践细节有时间了会单独整理一篇文章分享。

DevOps 简介

DevOps 集文化理念、实践和工具于一身,可以提高企业高速交付应用程序和服务的能力,与使用传统软件开发和基础设施管理流程相比,能够帮助企业更快地发展和改进产品。

Goals

The goals of DevOps span the entire delivery pipeline. They include:

  • Improved deployment frequency;
  • Faster time to market;
  • Lower failure rate of new releases;
  • Shortened lead time between fixes;
  • Faster mean time to recovery (in the event of a new release crashing or otherwise disabling the current system).

DevOps 的 7 大关键实践在Kubernetes 平台下的落地

下面为基于 Kubernetes 的 CI/CD 流水线整体实践,使用 Kubernetes + Docker + Jenkins + Helm 等云原生工具链来构建 CI/CD 管道,基于 Google Cloud Platform 云平台构建:


1. Configuration Management
关于配置文件的管理我们统一使用 Kubernetes 提供的 configmap 和 secret 资源对象来实现,对于一般的应用配置使用 configmap 管理,对于敏感数据使用 secret 存储。

2. Release Management
关于发布管理,使用 Helm 工具统一以 Chart 的方式打包并发布应用,维护每个微服务的历史版本,可以按需回滚。应用的镜像版本统一存储到一个 Docker 私服。

3. Continuous Integration
关于持续集成整合了一套完全开源的工具链:Gitlab + Maven + Jenkins + TestNg + SonarQuebe + Allure。Jenkins 运行在 Kubernetes 集群中,所有的工具链都以容器的方式运行,按需定义并使用,无需单独安装维护。

4. Continuous Deployment
关于持续部署方面做的不是很好,没有形成完整的 Pipeline,整个自动化 Pipeline 进行到预发布环境,对于生产环境发布,研发人员自助式点击另一个单独的部署 Pipeline Job 进行部署,选择镜像版本进行发布。

5. Infrastructure as Code
基础架构即代码的指的是所有软件基础设施的管理维护都以代码的方式管理起来,对于基础设施资源的创建、销毁、变更,不再是人工通过界面化点触式管理,所有这些更改都基于代码的提交变更和一套自动化框架。比如知名的 Terraform 就是一个业界比较流行的 IaC 框架,专门用于代码化管理云基础设施。在 Infrastructure as Code 方面,其实 Kubernetes 完全契合这一点,所有的基础架构及应用服务都可以被抽象成 API 资源对象,我们只需要按需定义 yaml 资源文件即可快速生成相应的资源。比如我们需要一个 Redis 中间件,那么只需要编写一个声明式 yaml 文件,定义需要的配置、版本等信息即可以容器的方式在 k8s 集群中运行起来。最终只需要在 git 代码仓库维护这些 yaml 文件即可。

6. Test Automation
关于测试自动化,目前主要有三种类型的测试:单元测试、后端接口测试、UI 自动化测试。所有的这些测试都集成在 Jenkins pipeline 中。后端接口测试和 UI 测试除了在自动化 Pipeline 中运行,还会每天定时跑测试,最终的测试报告统一收集到 Reportportal 报表平台。

7. Application Performance Monitoring
关于应用监控采用云原生架构下的最佳实践: Prometheus 监控栈

关于日志采集基于 EFK 技术栈

相关文档

https://www.wikiwand.com/en/DevOps#/Definitions_and_history

Kubernetes 基于 EFK 技术栈的日志收集实践

发表于 2019-09-05 | 分类于 Kubernetes

之前写过一篇文章介绍了容器环境下日志管理的原理机制:从 Docker 到 Kubernetes 日志管理机制详解,文章内容偏理论,本文在该理论的支撑下具体实践 Kubernetes 下基于 EFK 技术栈的日志收集,本文偏实践,要想全面了解 Kubernetes 下日志收集管理机制,最好还是两篇文章顺序阅读。

本文不仅限于介绍怎么在 Kubernetes 集群部署 EFK 组件,还涉及到其他相关话题:EFK 组件简介、Kubernetes 环境下为什么选用 EFK 优于 ELK 组件、Fluentd 简介、Fluentd 工作机制等等。本文结构如下:

  • EFK 组件简介
  • Kubernetes 环境下为什么选用 EFK 优于 ELK 组件
  • Fluentd 简介
  • Fluentd 工作机制
  • Fluentd 配置的核心指令
  • Kubernetes EFK 日志收集架构
  • Kubernetes EFK 日志收集架构实践

EFK 组件简介


顾名思义,EFK 是 Elasticsearch、Fluentd、Kibana 三个开源软件组件的首字母缩写,EFK 是业界主流的容器时代日志收集处理的最佳解决方案。Fluentd 是一个日志收集器,负责从各个服务器节点抓取日志。Elasticsearch 是一个搜索存储引擎,可以存储 Fluentd 收集的日志,并提供查询服务。Kibana 是 Elasticsearch 的一个界面化组件,提供 UI 方式查询 Elasticsearch 存储的数据。三个组件组合起来工作就是 EFK,组件之间数据流是这样:Log 源(比如 Log 文件、k8s Pod 日志、Docker 日志驱动等等)—> Fluentd 收集 —> Elasticsearch 存储 —> Kibana UI 查看。

Kubernetes 环境下为什么选用 EFK 优于 ELK 组件

ELK 是 Elasticsearch、Logstash、Kibana 三个开源组件的缩写,可以看出和 EFK 的区别在于第二个字母,一个是 Logstash,一个是 Fluentd。

我们很早之前使用 ELK 技术栈收集云主机上部署的应用日志,每台虚拟主机部署一个 Logstash 实例收集应用程序目录日志,然后统一传输到一个 Logshtash 中央处理节点,处理后的数据存储到 Elasticsearch。这种方案虽然可以实现日志的统一收集处理,但是 Logstash 比较吃系统资源,基本上资源占用堪比一个 Java 微服务,一个简单的抓取节点就大概需要 1 GB 的内存,我们知道云主机的机器资源是非常昂贵的,所以这种方案对资源浪费比较大,不是很推荐。之前我们基于该 ELK 方案的日志收集大致架构图如下:收集大概 13 个微服务日志,其中 Logstash Server 端平均内存使用能达到 8GB +,显然 Logstash 比较重量级,对系统资源的消耗可见一斑。

从上面的实践案例可以知道 ELK 技术栈的缺陷在于 Log 收集器 Logstash 比较重量级,对系统资源消耗比较大。那么有没有更加轻量级的替代方案呢?答案是有的,比如 Elastic 的 beats 家族的 Filebeat 就可以取代 Logstash 作为日志抓取器,还有 Fluentd 也可以作为 Logstash 替代品,Fluentd 也是本文要讲的重头戏。为什么我们选用 Fluentd 而不用 Filebeat 呢,个人认为 Fluentd 兼具日志抓取、收集、处理、轻量级的特性,拥有丰富的插件生态,是日志解决方案的神器。而 Filebeat 功能重点在于日志的抓取、轻量级,但是对于日志的处理功能不是很强大。

Fluentd 的轻量级体现在它本身核心代码是用 C 语言编写,更接近操作系统底层语言,所以一般性能是比较高的,据官方介绍 30~40 MB 内存就能处理 13000 日志事件/秒/核,由此可见其性能是多么强大,所以对比 Logstash 而言,用 30 MB 内存就能解决的问题当然选择 Fluentd 了。

Fluentd 简介

Fluentd 是一个开源的日志收集器,它统一了日志的收集和处理逻辑,多种不同来源的日志都可以通过 Fluentd 这个单一的工具统一收集,然后统一存储到单个或多个存储后端。

像很早之前没有类似 Fluentd 之类的工具的话,为了收集日志可能会有各种五花八门的方法(工具),比如:Shell 脚本分析日志文件、服务器 syslog 收集、rsync 定时同步,scp 拷贝日志文件到统一的存储服务器等等,这么做显然带来的问题是我们要维护各种日志收集端工具,由于各种工具使用上不统一,对整个日志系统的维护人员来说不亚于一场灾难。我们可以用一张图来描述这种复杂、错乱的场景:

针对上述存在的问题,Fluentd 插件式架构很好地解决了该问题,所有数据收集端和存储端都可以通过 Fluentd 使用不同的插件统一起来,这么做带来的最大好处就是大大简化了日志收集的架构,整个架构都以 Fuentd 为核心,不再需要维护人员掌握、维护各种乱七八糟的小工具。上面复杂、错乱的数据收集架构就可以简化为以 Fluentd 为核心的简单架构了:

关于 Fluentd 更多的介绍见官方文档:https://docs.fluentd.org/ 。

Fluentd 工作机制

Fluentd 和其他日志收集器的工作原理类似,对数据的处理流程也分为三大阶段:收集—>处理、过滤—>输出。这三个处理阶段都有不同的插件支持,可以灵活组合。在 Fluentd 中事件是 整个数据流处理的基本单位,fluentd 的 input 插件每接收到一条日志都会将其封装成一个 fluentd 事件,然后发送给 fluentd 引擎处理,fluentd 引擎根据事件中包含的 tab 匹配不同的 filter 插件进行事件的处理,处理完后发送到 output 插件,output 插件根据事件中的 tag 匹配事件,将匹配到的事件发送到对应的后端。

Fluentd 事件由三部分组成:

  • tag: . 分隔的字符串,供 fluentd 路由引擎路由使用;
  • time: 由输入插件指定的事件发生的时间戳,必须符合 Unix 时间格式;
  • record: 日志内容;

Fluentd 配置的核心指令

上面简单介绍了下 Fluentd 对日志数据的处理流程,其是在这个流程中 Fluentd 的行为是通过其配置文件定义的,配置文件由一条条指令组成。下面是 Fluentd 配置文件的 6 大指令,是 Fluentd 配置的核心指令:

  • source directives determine the input sources.
  • match directives determine the output destinations.
  • filter directives determine the event processing pipelines.
  • system directives set system wide configuration.
  • label directives group the output and filter for internal
    routing
  • @include directives include other files.

关于每条指令在具体配置文件中如何使用这里不再赘述,详情见:https://docs.fluentd.org/configuration/config-file

Kubernetes EFK 日志收集架构

之前介绍过 Kubernetes 日志管理机制:再 k8s 每个节点上,kubelet 会为每个容器的日志创建一个软链接,软连接存储路径为:/var/log/containers/,软连接会链接到 /var/log/pods/ 目录下相应 pod 目录的容器日志,被链接的日志文件也是软链接,最终链接到 Docker 容器引擎的日志存储目录:/var/lib/docker/container 下相应容器的日志。所以 /var/log 和 /var/lib/docker/container 目录是整个节点所有容器日志的统一存储地方,这就为 Fluentd 日志收集提供了很大的方便。

针对上述 k8s 日志管理机制,Kubernetes 官方给出了推荐的日志收集方案:以 DaemonsSet 的方式在 k8s 集群每个节点部署一个节点级的 Fluentd 日志收集器,Fluentd Pod 在启动时挂载了宿主机的 /var/log 和 /var/lib/docker/container 目录,因此可以直接对宿主机目录中的容器日志读取并传输到存储后端:Elasticsearch。然后 Kibana 和 Elasticsearch 对接即可实时查看收集到的日志数据。

Kubernetes EFK 日志收集架构实践

上面介绍了 Kubernetes EFK 日志收集架构,本节基于该架构进行实践。我们使用 Kubernetes 官方提供的 EFK 组件 mainfest 文件进行部署,Github 仓库目录为:
https://github.com/kubernetes/kubernetes/tree/master/cluster/addons/fluentd-elasticsearch

该目录下存储了 EFK 三大组件的部署 yaml 资源文件,有两点需要说明下:

  • Elasticsearch 数据持久化:默认 EmptyDir 的方式,这种方式在 Pod 重新调度后数据会丢失,不过为了实验,所以在这里就不进行修改了,使用默认的即可;
  • Kibana deployment 文件需要修改下:去掉 SERVER_BASEPATH 环境变量,要不然部署后访问会 404;

Kubernetes EFK 部署

进入 fluentd-elasticsearch 目录,执行:

1
kubectl  create -f .

查看 Pod 状态,确保每个组件启动成功,一般都会启动成功的,这里基本没什么坑。

Kibana 对外访问

接下来将 Kibana 服务从 Kubernetes 集群对外暴露出来,实现对外访问,我们使用 Kong 网关对外暴露服务。具体 Kong 网关在 Kubernetes 的使用在这里不再赘述,如想了解见这里:Kong 微服务网关在 Kubernetes 的实践。

Konga 配置 Kibana 服务对外访问:

  1. 创建 kibana-logging Kong Service
  2. 创建 Kong 路由到 kibana-logging Kong Service

kibnana.kong.com 绑定 host 到集群节点 IP 即可访问查看收集的日志:

相关文档

https://kubernetes.io/docs/concepts/cluster-administration/logging/ | Kubernetes 日志架构
https://www.fluentd.org/architecture | What is fluentd?
https://docs.fluentd.org/quickstart/life-of-a-fluentd-event | Life of a Fluentd event

123…14

haohao

Talk is cheap. Show me the code.

134 日志
35 分类
43 标签
GitHub CSDN 开源中国 E-Mail
© 2017 — 2021 haohao
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.3
访问人数 总访问量 次