wenmo8 发布的文章

前言

C 语言是一门抽象的面向过程的语言,C 语言广泛应用于底层开发,C 语言在计算机体系中占据着不可替代的作用,可以说 C 语言是编程的基础,也就是说,不管你学习任何语言,都应该把 C 语言放在首先要学的位置上。下面这张图更好的说明 C 语言的重要性

图片

可以看到,C 语言是一种底层语言,是一种系统层级的语言,操作系统就是使用 C 语言来编写的,比如 Windows、Linux、UNIX 。如果说其他语言是光鲜亮丽的外表,那么 C 语言就是灵魂,永远那么朴实无华。

C 语言特性

那么,既然 C 语言这么重要,它有什么值得我们去学的地方呢?我们不应该只因为它重要而去学,我们更在意的是学完我们能学会什么,能让我们获得什么。

C 语言的设计

C 语言是 1972 年,由贝尔实验室的丹尼斯·里奇(Dennis Ritch)肯·汤普逊(Ken Thompson)在开发 UNIX 操作系统时设计了C语言。C 语言是一门流行的语言,它把计算机科学理论和工程实践理论完美的融合在一起,使用户能够完成模块化的编程和设计。

计算机科学理论:简称 CS、是系统性研究信息与计算的理论基础以及它们在计算机系统中如何实现与应用的实用技术的学科。

C 语言具有高效性

C 语言是一门高效性语言,它被设计用来充分发挥计算机的优势,因此 C 语言程序运行速度很快,C 语言能够合理了使用内存来获得最大的运行速度

C 语言具有可移植性

C 语言是一门具有可移植性的语言,这就意味着,对于在一台计算机上编写的 C 语言程序可以在另一台计算机上轻松地运行,从而极大的减少了程序移植的工作量。

C 语言特点

  • C 语言是一门简洁的语言,因为 C 语言设计更加靠近底层,因此不需要众多 Java 、C# 等高级语言才有的特性,程序的编写要求不是很严格。
  • C 语言具有结构化控制语句,C 语言是一门结构化的语言,它提供的控制语句具有结构化特征,如 for 循环、if⋯ else 判断语句和 switch 语句等。
  • C 语言具有丰富的数据类型,不仅包含有传统的字符型、整型、浮点型、数组类型等数据类型,还具有其他编程语言所不具备的数据类型,比如指针。
  • C 语言能够直接对内存地址进行读写,因此可以实现汇编语言的主要功能,并可直接操作硬件。
  • C 语言速度快,生成的目标代码执行效率高。

下面让我们通过一个简单的示例来说明一下 C 语言

入门级 C 语言程序

下面我们来看一个很简单的 C 语言程序,我是 mac 电脑,所以我使用的是 xcode 进行开发,我觉得工具无所谓大家用着顺手就行。

第一个 C 语言程序

#include <stdio.h>

int main(int argc, const char * argv[]) {
    printf("Hello, World!\n");
    
    printf("My Name is cxuan \n");
    
    return 0;
}

你可能不知道这段代码是什么意思,不过别着急,我们先运行一下看看结果。

图片

这段程序输出了 Hello,World! 和 My Name is cxuan,最后一行是程序的执行结果,表示这段程序是否有错误。下面我们解释一下各行代码的含义。

首先,第一行的 #include <stdio.h>, 这行代码包含另一个文件,这一行告诉编译器把 stdio.h 的内容包含在当前程序中。stdio.h 是 C 编译器软件包的标准部分,它能够提供键盘输入和显示器输出。

什么是 C 标准软件包?C 是由 Dennis M 在1972年开发的通用,过程性,命令式计算机编程语言。C标准库是一组 C 语言内置函数,常量和头文件,例如<stdio.h>,<stdlib.h>,<math.h>等。此库将用作 C 程序员的参考手册。

我们后面会介绍 stdio.h ,现在你知道它是什么就好。

在 stdio.h 下面一行代码就是 main 函数。

C 程序能够包含一个或多个函数,函数是 C 语言的根本,就和方法是 Java 的基本构成一样。main() 表示一个函数名,int 表示的是 main 函数返回一个整数。void 表明 main() 不带任何参数。这些我们后面也会详细说明,只需要记住 int 和 void 是标准 ANSI C 定义 main() 的一部分(如果使用 ANSI C 之前的编译器,请忽略 void)。

然后是 /*一个简单的 C 语言程序*/ 表示的是注释,注释使用 /**/ 来表示,注释的内容在两个符号之间。这些符号能够提高程序的可读性。

注意:注释只是为了帮助程序员理解代码的含义,编译器会忽略注释

下面就是 { ,这是左花括号,它表示的是函数体的开始,而最后的右花括号 } 表示函数体的结束。{ } 中间是书写代码的地方,也叫做代码块。

int number 表示的是将会使用一个名为 number 的变量,而且 number 是 int 整数类型。

number = 11 表示的是把值 11 赋值给 number 的变量。

printf(Hello,world!\n); 表示调用一个函数,这个语句使用 printf() 函数,在屏幕上显示 Hello,world , printf() 函数是 C 标准库函数中的一种,它能够把程序运行的结果输出到显示器上。而代码 \n 表示的是 换行,也就是另起一行,把光标移到下一行。

然后接下来的一行 printf() 和上面一行是一样的,我们就不多说了。最后一行 printf() 有点意思,你会发现有一个 %d 的语法,它的意思表示的是使用整形输出字符串。

代码块的最后一行是 return 0,它可以看成是 main 函数的结束,最后一行是代码块 } ,它表示的是程序的结束。

好了,我们现在写完了第一个 C 语言程序,有没有对 C 有了更深的认识呢?肯定没有。。。这才哪到哪,继续学习吧。

现在,我们可以归纳为 C 语言程序的几个组成要素,如下图所示

图片

C 语言执行流程

C 语言程序成为高级语言的原因是它能够读取并理解人们的思想。然而,为了能够在系统中运行 hello.c 程序,则各个 C 语句必须由其他程序转换为一系列低级机器语言指令。这些指令被打包作为可执行对象程序,存储在二进制磁盘文件中。目标程序也称为可执行目标文件。

在 UNIX 系统中,从源文件到对象文件的转换是由编译器执行完成的。

gcc -o hello hello.c

gcc 编译器驱动从源文件读取 hello.c ,并把它翻译成一个可执行文件 hello。这个翻译过程可用如下图来表示

图片

这就是一个完整的 hello world 程序执行过程,会涉及几个核心组件:预处理器、编译器、汇编器、连接器,下面我们逐个击破。

  • 预处理阶段(Preprocessing phase),预处理器会根据开始的 # 字符,修改源 C 程序。#include <stdio.h> 命令就会告诉预处理器去读系统头文件 stdio.h 中的内容,并把它插入到程序作为文本。然后就得到了另外一个 C 程序hello.i,这个程序通常是以 .i为结尾。

  • 然后是 编译阶段(Compilation phase),编译器会把文本文件 hello.i 翻译成文本hello.s,它包括一段汇编语言程序(assembly-language program)

  • 编译完成之后是汇编阶段(Assembly phase),这一步,汇编器 as会把 hello.s 翻译成机器指令,把这些指令打包成可重定位的二进制程序(relocatable object program)放在 hello.c 文件中。它包含的 17 个字节是函数 main 的指令编码,如果我们在文本编辑器中打开 hello.o 将会看到一堆乱码。

  • 最后一个是链接阶段(Linking phase),我们的 hello 程序会调用 printf 函数,它是 C 编译器提供的 C 标准库中的一部分。printf 函数位于一个叫做 printf.o文件中,它是一个单独的预编译好的目标文件,而这个文件必须要和我们的 hello.o 进行链接,连接器(ld) 会处理这个合并操作。结果是,hello 文件,它是一个可执行的目标文件(或称为可执行文件),已准备好加载到内存中并由系统执行。

你需要理解编译系统做了什么

对于上面这种简单的 hello 程序来说,我们可以依赖编译系统(compilation system)来提供一个正确和有效的机器代码。然而,对于我们上面讲的程序员来说,编译器有几大特征你需要知道

  • 优化程序性能(Optimizing program performance),现代编译器是一种高效的用来生成良好代码的工具。对于程序员来说,你无需为了编写高质量的代码而去理解编译器内部做了什么工作。然而,为了编写出高效的 C 语言程序,我们需要了解一些基本的机器码以及编译器将不同的 C 语句转化为机器代码的过程。
  • 理解链接时出现的错误(Understanding link-time errors),在我们的经验中,一些非常复杂的错误大多是由链接阶段引起的,特别是当你想要构建大型软件项目时。
  • 避免安全漏洞(Avoiding security holes),近些年来,缓冲区溢出(buffer overflow vulnerabilities)是造成网络和 Internet 服务的罪魁祸首,所以我们有必要去规避这种问题。

系统硬件组成

为了理解 hello 程序在运行时发生了什么,我们需要首先对系统的硬件有一个认识。下面这是一张 Intel 系统产品的模型,我们来对其进行解释

图片

  • 总线(Buses):在整个系统中运行的是称为总线的电气管道的集合,这些总线在组件之间来回传输字节信息。通常总线被设计成传送定长的字节块,也就是 字(word)。字中的字节数(字长)是一个基本的系统参数,各个系统中都不尽相同。现在大部分的字都是 4 个字节(32 位)或者 8 个字节(64 位)。

图片

  • I/O 设备(I/O Devices):Input/Output 设备是系统和外部世界的连接。上图中有四类 I/O 设备:用于用户输入的键盘和鼠标,用于用户输出的显示器,一个磁盘驱动用来长时间的保存数据和程序。刚开始的时候,可执行程序就保存在磁盘上。

    每个I/O 设备连接 I/O 总线都被称为控制器(controller) 或者是 适配器(Adapter)。控制器和适配器之间的主要区别在于封装方式。控制器是 I/O 设备本身或者系统的主印制板电路(通常称作主板)上的芯片组。而适配器则是一块插在主板插槽上的卡。无论组织形式如何,它们的最终目的都是彼此交换信息。

  • 主存(Main Memory),主存是一个临时存储设备,而不是永久性存储,磁盘是 永久性存储 的设备。主存既保存程序,又保存处理器执行流程所处理的数据。从物理组成上说,主存是由一系列 DRAM(dynamic random access memory) 动态随机存储构成的集合。逻辑上说,内存就是一个线性的字节数组,有它唯一的地址编号,从 0 开始。一般来说,组成程序的每条机器指令都由不同数量的字节构成,C 程序变量相对应的数据项的大小根据类型进行变化。比如,在 Linux 的 x86-64 机器上,short 类型的数据需要 2 个字节,int 和 float 需要 4 个字节,而 long 和 double 需要 8 个字节。

  • 处理器(Processor)CPU(central processing unit)  或者简单的处理器,是解释(并执行)存储在主存储器中的指令的引擎。处理器的核心大小为一个字的存储设备(或寄存器),称为程序计数器(PC)。在任何时刻,PC 都指向主存中的某条机器语言指令(即含有该条指令的地址)。

    从系统通电开始,直到系统断电,处理器一直在不断地执行程序计数器指向的指令,再更新程序计数器,使其指向下一条指令。处理器根据其指令集体系结构定义的指令模型进行操作。在这个模型中,指令按照严格的顺序执行,执行一条指令涉及执行一系列的步骤。处理器从程序计数器指向的内存中读取指令,解释指令中的位,执行该指令指示的一些简单操作,然后更新程序计数器以指向下一条指令。指令与指令之间可能连续,可能不连续(比如 jmp 指令就不会顺序读取)

    下面是 CPU 可能执行简单操作的几个步骤

  • 加载(Load):从主存中拷贝一个字节或者一个字到内存中,覆盖寄存器先前的内容

  • 存储(Store):将寄存器中的字节或字复制到主存储器中的某个位置,从而覆盖该位置的先前内容

  • 操作(Operate):把两个寄存器的内容复制到 ALU(Arithmetic logic unit)。把两个字进行算术运算,并把结果存储在寄存器中,重写寄存器先前的内容。

算术逻辑单元(ALU)是对数字二进制数执行算术和按位运算的组合数字电子电路。

  • 跳转(jump):从指令中抽取一个字,把这个字复制到程序计数器(PC) 中,覆盖原来的值

剖析 hello 程序的执行过程

前面我们简单的介绍了一下计算机的硬件的组成和操作,现在我们正式介绍运行示例程序时发生了什么,我们会从宏观的角度进行描述,不会涉及到所有的技术细节

刚开始时,shell 程序执行它的指令,等待用户键入一个命令。当我们在键盘上输入了 ./hello 这几个字符时,shell 程序将字符逐一读入寄存器,再把它放到内存中,如下图所示

图片

当我们在键盘上敲击回车键的时候,shell 程序就知道我们已经结束了命令的输入。然后 shell 执行一系列指令来加载可执行的 hello 文件,这些指令将目标文件中的代码和数据从磁盘复制到主存。

利用 DMA(Direct Memory Access) 技术可以直接将磁盘中的数据复制到内存中,如下

图片

一旦目标文件中 hello 中的代码和数据被加载到主存,处理器就开始执行 hello 程序的 main 程序中的机器语言指令。这些指令将 hello,world\n 字符串中的字节从主存复制到寄存器文件,再从寄存器中复制到显示设备,最终显示在屏幕上。如下所示

图片

高速缓存是关键

上面我们介绍完了一个 hello 程序的执行过程,系统花费了大量时间把信息从一个地方搬运到另外一个地方。hello 程序的机器指令最初存储在磁盘上。当程序加载后,它们会拷贝到主存中。当 CPU 开始运行时,指令又从内存复制到 CPU 中。同样的,字符串数据 hello,world \n 最初也是在磁盘上,它被复制到内存中,然后再到显示器设备输出。从程序员的角度来看,这种复制大部分是开销,这减慢了程序的工作效率。因此,对于系统设计来说,最主要的一个工作是让程序运行的越来越快。

由于物理定律,较大的存储设备要比较小的存储设备慢。而由于寄存器和内存的处理效率在越来越大,所以针对这种差异,系统设计者采用了更小更快的存储设备,称为高速缓存存储器(cache memory, 简称为 cache 高速缓存),作为暂时的集结区域,存放近期可能会需要的信息。如下图所示

图片

图中我们标出了高速缓存的位置,位于高速缓存中的 L1高速缓存容量可以达到数万字节,访问速度几乎和访问寄存器文件一样快。容量更大的 L2 高速缓存通过一条特殊的总线链接 CPU,虽然 L2 缓存比 L1 缓存慢 5 倍,但是仍比内存要快 5 - 10 倍。L1 和 L2 是使用一种静态随机访问存储器(SRAM) 的硬件技术实现的。最新的、处理器更强大的系统甚至有三级缓存:L1、L2 和 L3。系统可以获得一个很大的存储器,同时访问速度也更快,原因是利用了高速缓存的 局部性原理。

Again:入门程序细节

现在,我们来探讨一下入门级程序的细节,由浅入深的来了解一下 C 语言的特性。

#include<stdio.h>

我们上面说到,#include<stdio.h> 是程序编译之前要处理的内容,称为编译预处理命令。

预处理命令是在编译之前进行处理。预处理程序一般以 # 号开头。

所有的 C 编译器软件包都提供 stdio.h 文件。该文件包含了给编译器使用的输入和输出函数,比如 println() 信息。该文件名的含义是标准输入/输出 头文件。通常,在 C 程序顶部的信息集合被称为 头文件(header)

C 的第一个标准是由 ANSI 发布的。虽然这份文档后来被国际标准化组织(ISO)采纳并且 ISO 发布的修订版也被 ANSI 采纳了,但名称 ANSI C(而不是 ISO C) 仍被广泛使用。一些软件开发者使用ISO C,还有一些使用 Standard C

C 标准库

除了 <sdtio.h> 外,C 标准库还包括下面这些头文件

图片

<assert.h>

提供了一个名为 assert 的关键字,它用于验证程序作出的假设,并在假设为假输出诊断消息。

<ctype.h>

C 标准库的 ctype.h 头文件提供了一些函数,可以用于测试和映射字符。

这些字符接受 int 作为参数,它的值必须是 EOF 或者是一个无符号字符

EOF是一个计算机术语,为 End Of File 的缩写,在操作系统中表示资料源无更多的资料可读取。资料源通常称为档案或串流。通常在文本的最后存在此字符表示资料结束。

C 标准库的 errno.h 头文件定义了整数变量 errno,它是通过系统调用设置的,这些库函数表明了什么发生了错误。

C 标准库的 float.h 头文件包含了一组与浮点值相关的依赖于平台的常量。

limits.h 头文件决定了各种变量类型的各种属性。定义在该头文件中的宏限制了各种变量类型(比如 char、int 和 long)的值。

locale.h 头文件定义了特定地域的设置,比如日期格式和货币符号

math.h 头文件定义了各种数学函数和一个宏。在这个库中所有可用的功能都带有一个 double 类型的参数,且都返回 double 类型的结果。

setjmp.h 头文件定义了宏 setjmp()、函数 longjmp() 和变量类型 jmp_buf,该变量类型会绕过正常的函数调用和返回规则。

signal.h 头文件定义了一个变量类型 sig_atomic_t、两个函数调用和一些宏来处理程序执行期间报告的不同信号。

stdarg.h 头文件定义了一个变量类型 va_list 和三个宏,这三个宏可用于在参数个数未知(即参数个数可变)时获取函数中的参数。

stddef .h 头文件定义了各种变量类型和宏。这些定义中的大部分也出现在其它头文件中。

stdlib .h 头文件定义了四个变量类型、一些宏和各种通用工具函数。

string .h 头文件定义了一个变量类型、一个宏和各种操作字符数组的函数。

time.h 头文件定义了四个变量类型、两个宏和各种操作日期和时间的函数。

main() 函数

main 函数听起来像是调皮捣蛋的孩子故意给方法名起一个 主要的 方法,来告诉他人他才是这个世界的中心。但事实却不是这样,而 main() 方法确实是世界的中心。

C 语言程序一定从 main() 函数开始执行,除了 main() 函数外,你可以随意命名其他函数。通常,main 后面的 () 中表示一些传入信息,我们上面的那个例子中没有传递信息,因为圆括号中的输入是 void 。

除了上面那种写法外,还有两种 main 方法的表示方式,一种是 void main(){} ,一种是 int main(int argc, char* argv[]) {}

  • void main() 声明了一个带有不确定参数的构造方法
  • int main(int argc, char* argv[]) {} 其中的 argc 是一个非负值,表示从运行程序的环境传递到程序的参数数量。它是指向 argc + 1 指针数组的第一个元素的指针,其中最后一个为null,而前一个(如果有的话)指向表示从主机环境传递给程序的参数的字符串。如果argv [0]不是空指针(或者等效地,如果argc> 0),则指向表示程序名称的字符串,如果在主机环境中无法使用程序名称,则该字符串为空。

注释

在程序中,使用 /**/ 的表示注释,注释对于程序来说没有什么实际用处,但是对程序员来说却非常有用,它能够帮助我们理解程序,也能够让他人看懂你写的程序,我们在开发工作中,都非常反感不写注释的人,由此可见注释非常重要。

图片

C 语言注释的好处是,它可以放在任意地方,甚至代码在同一行也没关系。较长的注释可以多行表示,我们使用 /**/ 表示多行注释,而 // 只表示的是单行注释。下面是几种注释的表示形式

// 这是一个单行注释

/* 多行注释用一行表示 */

/*
  多行注释用多行表示
    多行注释用多行表示
      多行注释用多行表示
        多行注释用多行表示

*/

函数体

在头文件、main 方法后面的就是函数体(注释一般不算),函数体就是函数的执行体,是你编写大量代码的地方。

变量声明

在我们入门级的代码中,我们声明了一个名为 number 的变量,它的类型是 int,这行代码叫做 声明,声明是 C 语言最重要的特性之一。这个声明完成了两件事情:定义了一个名为 number 的变量,定义 number 的具体类型。

int 是 C 语言的一个 关键字(keyword),表示一种基本的 C 语言数据类型。关键字是用于语言定义的。不能使用关键字作为变量进行定义。

示例中的 number 是一个 标识符(identifier),也就是一个变量、函数或者其他实体的名称。

变量赋值

在入门例子程序中,我们声明了一个 number 变量,并为其赋值为 11,赋值是 C 语言的基本操作之一。这行代码的意思就是把值 1 赋给变量 number。在执行 int number 时,编译器会在计算机内存中为变量 number 预留空间,然后在执行这行赋值表达式语句时,把值存储在之前预留的位置。可以给 number 赋不同的值,这就是 number 之所以被称为 变量(variable) 的原因。

图片

printf 函数

在入门例子程序中,有三行 printf(),这是  C 语言的标准函数。圆括号中的内容是从 main 函数传递给 printf 函数的。参数分为两种:实际参数(actual argument) 和 形式参数(formal parameters)。我们上面提到的 printf 函数括号中的内容,都是实参。

return 语句

在入门例子程序中,return 语句是最后一条语句。int main(void) 中的 int 表明 main() 函数应返回一个整数。有返回值的 C 函数要有 return 语句,没有返回值的程序也建议大家保留 return 关键字,这是一种好的习惯或者说统一的编码风格。

分号

在 C 语言中,每一行的结尾都要用 ; 进行结束,它表示一个语句的结束,如果忘记或者忽略分号会被编译器提示错误。

关键字

下面是 C 语言中的关键字,C 语言的关键字一共有 32 个,根据其作用不同进行划分

数据类型关键字

数据类型的关键字主要有 12 个,分别是

  • char: 声明字符型变量或函数
  • double: 声明双精度变量或函数
  • float: 声明浮点型变量或函数
  • int : 声明整型变量或函数
  • long: 声明长整型变量或函数
  • short : 声明短整型变量或函数
  • signed : 声明有符号类型变量或函数
  • _Bool:  声明布尔类型
  • _Complex :声明复数
  • _Imaginary: 声明虚数
  • unsigned : 声明无符号类型变量或函数
  • void : 声明函数无返回值或无参数,声明无类型指针

控制语句关键字

控制语句循环的关键字也有 12 个,分别是

循环语句

  • for : for 循环,使用的最多
  • do :循环语句的前提条件循环体
  • while:循环语句的循环条件
  • break : 跳出当前循环
  • continue:结束当前循环,开始下一轮循环

条件语句

  • if:条件语句的判断条件
  • else : 条件语句的否定分支,与 if 连用
  • goto: 无条件跳转语句

开关语句

  • switch: 用于开关语句
  • case:开关语句的另外一种分支
  • default : 开关语句中的其他分支

返回语句

retur:子程序返回语句(可以带参数,也看不带参数)

存储类型关键字

  • auto : 声明自动变量 一般不使用
  • extern : 声明变量是在其他文件正声明(也可以看做是引用变量)
  • register : 声明寄存器变量
  • static: 声明静态变量

其他关键字

  • const: 声明只读变量
  • sizeof : 计算数据类型长度
  • typedef: 用以给数据类型取别名
  • volatile : 说明变量在程序执行中可被隐含地改变


后记

这篇文章我们先介绍了 C 语言的特性,C 语言为什么这么火,C 语言的重要性,之后我们以一道 C 语言的入门程序讲起,我们讲了 C 语言的基本构成要素,C 语言在硬件上是如何运行的,C 语言的编译过程和执行过程等,在这之后我们又加深讲解了一下入门例子程序的组成特征。

如果你觉得这篇文章不错的的话,欢迎小伙伴们四连走起:点赞、在看、留言、分享。你的四连是我更文的动力。


这篇文章是 C 语言系列第三篇,之前两篇见

哦!这该死的 C 语言!

C 语言基础,来喽!

下面我们来介绍一下 C 语言中一个非常重要的概念 - 函数 (function)。首先就要先给函数下一个定义,函数就是完成特定任务的独立代码单元,这也就是说,一个函数肯定是要为了完成某种功能的,比如一个函数它能够执行加法运算,比如一个函数能交换两个数的值,还有一些函数可能只是为了打印某些东西等等。

函数也可以把很多大的任务拆分成一个个小的任务,通过设计每个小的任务来完成一个大的功能。一个设计优良的函数能够把程序中不需要了解的细节隐藏起来,从而使整个程序结构更加清晰,降低程序的修改难度。

C 语言程序由许多小的函数组成,一个程序会被保存在多个源文件中,每个文件可以单独编译,并可以与库中已编译过的函数一起加载。

下面我们通过一个例子来讨论一下函数是如何创建并使用的。

函数创建以及使用

函数的创建和使用会分为三个步骤:

  • 函数原型 ( function type ):这个是创建函数定义,也叫函数声明,能够表明一个文件中有哪些函数。
  • 函数调用 ( function call ):调用函数的位置,函数被定义出来肯定是要使用它的,在哪里使用的这个函数就被称为函数调用。
  • 函数定义 ( function definition ):这个就是函数的具体要干的什么事儿,也就是函数的具体逻辑是什么。

这么一看,函数和变量简直一模一样了,函数需要原型、调用和定义,而变量也需要这些,只不过变量还可以把原型和定义一起表示。

#include <stdio.h>int num; // 变量原型 int sum(int,int); // 函数原型 int main(void){ num = 12; // 变量定义  int num2 = 11; // 函数原型 + 函数定义   int all = sum(num,num2); // 变量使用 ,函数使用  printf("all = %d",all);  return 0;} // 函数定义 int sum(int a,int b){  return a + b;}

上面这段代码很好的列举了变量的定义以及函数的定义。

我们首先定义了一个 num 变量,这个就是变量的原型,然后在 main 函数中使用这个变量,就是变量的定义和使用,当然变量也可以直接使用原型 + 定义的方式( 上面的 num2 ),sum 函数演示了函数的原型、定义和使用。这里注意一点,main 函数比较特殊,它是所有方法的入口,而且 main 函数无需定义原型就能直接使用。

上面这段代码被一起保存在一个文件中,当然你也可以把它们保存在不同的文件中,只不过把它们放在同一个文件中我们在演示的时候比较方便,还有一点就是能够一起进行编译,这两个函数也可以定义在不同的文件中,分别进行编译,这样的好处是使程序更加易于维护,代码读起来更加顺畅,事实上项目中也是采用的单独编译的方式。当然你也可以把所有的功能都写在 main 函数中,只不过这样不易于维护,也不符合项目开发标准。

一个完整的函数定义形同如下:

返回值类型 函数名(参数列表){    函数体(函数的具体功能)}

注意我们上面说的只是一个完整的函数定义,而不是每个函数必须都要有返回值类型、参数列表、函数体,只有函数名是必须的(这个肯定好理解)。

当然也有函数定义出来什么都没有做,这就相当于是一个空函数,C 语言默认是允许空函数出现的,比如下面函数就是一个空函数。

sort(){}

sort 函数不执行任何操作也不返回任何值,这种函数可以在程序开发期间用于保留位置,留待以后再填充代码

程序其实就是一些变量和函数的集合,函数之间的通信可以通过函数参数、返回值来进行,函数通过传递参数,进行一系列的逻辑计算后,把返回值返回回去,以此达到函数交流、通信的目的。

对于函数来说,我们需要了解的两个关键点是参数列表返回值

函数参数

对于上面的 sum 函数来说,它的函数参数有两个,分别是 int 类型的 a 和 b,像这种在函数定义的括号中的变量被称为函数参数,这两个变量 a 和 b 也叫做形式参数,简称形参。

和定义在函数中的变量一样,形式参数也是局部变量,这些都属于函数私有的,作用域范围都是从进入函数开始起作用到函数执行完成后作用结束。

当函数接受参数时,函数原型用逗号分隔的列表指明参数的数量和类型,函数原型中你可以使用下面方式定义。

int sum(int a,int b); // 函数原型

也可以省略具体的变量名称,使用下面这种方式进行定义。

int sum(int,int); // 函数原型

在函数原型中没有定义变量,只是声明了两个 int 类型的参数。

除了形参之外,还有一个叫做实际参数 ( 实参 ) 的概念,就对应于上面代码中的 sum(num,num2),因为在调用 sum 的时候是知道 num 和 num2 的具体值的,像这种在调用函数中对参数进行传值的参数被称为实参。

简单点来说就是 形式参数是被调用函数中的变量,实际参数是调用函数赋给被调函数的具体值。实际参数可以是常量、变量,或甚至是更复杂的表达式。

被调函数不知道也不关心传入的数值是来自常量、变量还是一般表达式。实参在把值传递给函数的时候,其实是把值拷贝给被调函数的形式参数,所以无论被调函数对拷贝数据进行什么操作,都不会影响主调函数中的原始数据。

如下代码所示

#include <stdio.h>int num; // 变量原型 void sum(int,int); // 函数原型 int main(void){ num = 12; // 变量定义  int num2 = 11; // 函数原型 + 函数定义   sum(num,num2); // 变量使用 ,函数使用  printf("num = %d, num2 = %d",num,num2);  return 0;} // 函数定义 void sum(int a,int b){  int sumAll =  a + b; printf("sumAll = %d\n",sumAll); }

从输出结果可以看出,只要把值传递给 sum 后,不论 sum 函数内部进行何种处理,都不会影响 main 函数中 num 和 num2 的值。

函数返回值

我们上面说过函数之间的通信可以通过函数参数、返回值来进行。函数参数的传递方向是由函数调用者 -> 被调函数,而函数返回值的方向是和参数传递的方向相反,也就是被调函数 -> 函数调用者。

图片

当然并不是所有的函数都需要返回值,而且 return 语句后面也不一定需要表达式,当 return 语句后面没有表达式时,函数不会向调用者返回值。返回值会通过

return 表达式

进行返回,这个返回值的表达式类型和函数定义的返回值类型是一致的。

我们还用上面的 sum 函数来举例子

int sum(int a,int b){  return a + b;}

可以看到,sum 函数的表达式返回了 a + b,这其实就是一个表达式。而我们可以看到上面的 int main 方法,它的返回值是 0 ,这就是返回了一个常量。

return 后面可以不返回任何值,只是单独写一个 return 也是允许的,不过这种方式相当于没有返回任何值,所以它的函数类型可以定义为 void ,如下代码所示:

// 函数定义 void sum(int a,int b){  int sumAll =  a + b;  printf("sumAll = %d\n",sumAll);  return ; }

使用 return 语句的另外一个作用是终止函数的执行,强制把控制返回给调用函数,如下代码所示:

// 函数定义 int sum(int a,int b){   int sumAll =  a + b;  printf("sumAll = %d\n",sumAll);    if(a + b > 0){   return sumAll;  }else{    return 0;  }   }

如果 a + b 的值大于 0 的话,会直接返回 a + b 的和,否则为 0 。这个 if 的控制流程就是强制把结果返回给函数调用者。如果在 if 控制流程后面添加代码的话,那么这段代码不会执行,但是编译却没有给出警告。

图片

在 Java 编辑器中,如果最后一行代码出现在 return 强制返回后面的话,编译器会给出警告或者错误提示这行代码不会被执行。

函数类型

这里需要再强调一下函数类型,定义函数的时候需要声明函数的类型,带返回值的函数类型与返回值类型相同,没有返回值的函数应该将其定义为 void 类型。在老版本的 C 编译器中,如果你没有声明函数类型,编译器会默认把函数当做 int 类型来处理,不过这都是早期的事儿了,现在 C 标准不再支持默认函数为 int 类型这种情况。

在编写函数的时候,你就需要考虑好函数的具体功能是什么,也就是这个函数做了哪些事情,需不需要返回值,如果需要返回值的话,它的返回类型是什么。

函数声明

如果大家学过 Java ,可能对 C 这种先声明再定义的方式很不习惯,为什么函数在定义前需要再单独声明一下呢?我直接定义函数不声明行吗?答案肯定是不行的。

这个先声明再使用一直是 C 语言的标准,标准没有为什么,这就是一个标准,但是这个标准却是一个历史遗留问题。

上世纪 70 年代,大部分计算机内存很小,处理速度也比较差,所以导致代码的运行>时间很长,效率很差,这时候进行我们就需要考虑内存占用和编译时间的问题。因为 C 语言开发的比较早,而且 C 又是和硬件直接打交道的,所以提前声明一下函数能够提前分配内存空间,提升效率。说白了还是效率问题。

还有为什么 C 语言不选择采用预编译一下呢?

参考自 https://www.zhihu.com/question/20567689

首先,C语言出现的很早,那时候编译器也是一个很复杂的东西,当时计算机的内存、外存都很小,编译器做的太大也是一个麻烦的事情,所以事先声明就成为一种规范,保留下来,目的是为了让编译器更简单,虽然这一切已经很过时了。

其次,预编译的成本很高,与脚本语言、解释语言不同,C语言项目的规模可以很大,比如操作系统一级的C语言工程,其源文件有几万个,涉及全局符号几十万个,这样规模的项目预编译一次的负担是很高的,如果是整个项目完全扫描一遍,遍历所有全局符号,再进行真正的编译,估计很多码农都会疯了,等待时间会特别长。

再次,C语言是一种静态链接的语言,如果一个项目被设计成只编译,不链接的方式,比如有些库就会被设计成这样,有些合作开发的项目里,组员之间有时候也只提供obj文件,那么某些全局符号可能就不包含在现有的代码里,那么预搜索就一定找不到某些符号,那么该怎么办?如果不提供声明,这个代码就没办法编译了。

基于以上几点考虑,所以C语言才设计成这样,对于开发者而言,不算友好,但也不算很糟糕,甚至在某些方面是有好处的。

对于一个函数来说,它的最终目的就是通过一系列的逻辑处理获得我们想要的结果,逻辑处理离不开各种程序控制语句,比如说 While 、for、do while 等,下面我们就要来讨论一下这些程序控制语句。

程序控制语句

在有些时候的某些程序可能会重复做一件事情,就应该让计算机做这些重复性的工作,这才是我们需要计算机的意义。毕竟,需要重复计算是使用计算机的主要原因。

C 语言中有很多用于重复计算的方法,我们下面先来介绍其中的一种 --- while 循环

while 循环

下面我们通过一段代码来看一下 while 循环的使用。

#include <stdio.h>int main(){ int i = 1; while (i <= 10) {  printf("%d\n", i);  i++; } return 0;}

这段代码首先声明了一个 i 变量,然后使用了 while 循环来判断 i 的值,当 i 的值 <= 10 的时候,就会执行 while 中的循环逻辑,否则即 i > 10 就会直接跳过循环,不会输出任何结果就直接返回 0 。

如果 i 的值在 10 以内,就会循环打印出来 i 的值。这就是 while 循环的作用。

用通俗易懂的语句来描述 while 循环:当某个判断条件为 true 的时候,循环执行 while 中的代码块。

流程图如下:

图片

在 while 循环中的一个关键点就是进入 while 循环的判断,上面代码就是判断 i <= 10 ,这个表达式是关系运算符的一种。

while循环经常依赖测试表达式作比较,这样的表达式被称为关系表达式,出现在关系表达式中间的运算符叫做关系运算符,下表是我们经常使用到的关系运算符。


运算符说明
<小于
<=小于或等于
==等于
>=大于或等于
>大于
!=不等于


这些运算符会不单单会出现在 while 循环中,实际上任何逻辑控制语句都会使用到这几种运算符。

这里需要说明一点,不能用关系运算符来比较字符串,比如 ch != '@' 。

虽然关系运算符可以用来比较浮点数,但是要注意:比较浮点数时,尽量只使用 < 和 > 。因为浮点数的舍入误差会导致在逻辑上应该相等的两数却不相等。例如,3乘以1/3的积是1.0。如果用把1/3表示成小数点后面6位数字,乘积则是 .999999,不等于 1。

for 循环

for 循环一个非常明显的特征就是把三个行为组合在一处,也就是初始化、判断、更新,如下代码所示。

#include <stdio.h>int main(){ for(int i = 1;i <= 10;i++){  printf("%d\n", i); } return 0;}

可以看到,上面代码中 for 循环分别做了三件事情,每个表达式用 ; 进行分隔。

  • int i = 0 相当于是对 i 进行初始化操作;
  • i <= 10 相当于对 i 进行一个逻辑判断,逻辑判断是判断是否进行下一次循环的关键。
  • i++ 相当于是更新 i 的值。

for 循环的一般形式定义如下:

for(表达式1;表达式2;表达式3){    语句;}

这里要注意的是,表达式 1 只在循环开始时执行一次,而表达式 3 是循环结束后再执行。表达式 2 可以省略,省略后默认值为 1,则判断为真,for 循环就会成为一个死循环。

for 循环的流程图如下

图片

do while 循环

一般来说,循环的方式可以分为两种:入口循环和出口循环,什么意思呢?入口循环是先进行循环,再执行每次循环要做的事情,比如上面的 while 循环、for 循环,他们都是先进行判断是否需要进行下一次循环,如果需要的话,才会打印出 i 的值,这就是入口循环。

而出口循环则是要先执行代码,再判断是否要进行下一次循环,即在循环的每次迭代之后检查测试条件,这保证了至少执行循环体中的内容一次,典型的出口循环就是 do ... while。

我们把上面的代码进行修改:

#include <stdio.h>int main(){ int i = 1; do{  printf("%d\n", i);  i++; }while(i <= 10);  return 0;}

从输出结果可以看到,do while 循环在执行完循环体后才执行测试条件,所以 do ... while 循环至少执行循环体一次,而 for 循环和 while 循环在执行循环体之前先执行测试条件,do ... while 的一般形式如下

do 代码while ( 表达式 );

do ... while 循环的流程图如下

图片

到现在为止, C 语言中的程序控制语句我们都了解了,那么该如何进行选择呢?

实际上上面我们已经稍微讨论了一下如何选择的问题了。

while 循环和 for 循环很类似,这两类循环都是先进行一次循环条件的判断,然后再执行具体的循环体操作,只要一次循环条件不满足则一次都不会执行;而 do ... while 循环会至少先进行一次循环,然后才会执行循环判断。

一般来说,使用 for 循环的场景比较多,因为 for 循环形式更加简洁,而且在 for 循环中,变量和判断以及更新的作用域都在循环体内,不会有其他外部代码来修改这些变量,更可控,在 while 和 do ... while 循环中,变量的更新不可控,而且代码也没有 for 循环可读性强。

break 和 continue

break 和 continue 相当于是循环体内领导者的这样一个角色,有了这两个角色存在,循环体内的代码会根据这两个关键字来判断是中断循环还是执行下一次循环。

C 语言中的 break 有两种用法:

  • 一种用法是用在循环体中,当 break 出现在循环体中时,会中断这个循环。
  • 一种用法是用在 switch 语句中,用作中断这个 switch 语句的 case 条件。

break 用于中断循环:如下代码所示

#include <stdio.h>int main(void){ for(int i = 1;i <= 10;i++){  if(i == 5){   break;  }  printf("i 的值 = %d\n",i); }  return 0;}

输出的结果是 i 的值 = 1 - 4, 当 i == 5 时,会进入到 if 判断中,if 判断会直接触发 break,break 用于跳出当前循环,当前是 for 循环,所以 break 会直接跳到 for 循环外面,也就是直接 return ,不会再打印 i 的值。

图片

continue 关键字用于跳过当前循环,执行下一次循环,它和 break 很相似但是有着本质的区别,break 是跳出循环,continue 是执行下一次循环,我们同样拿这个代码来说明,只需要把上面的 break 改成 continue 即可。

#include <stdio.h>int main(void){ for(int i = 1;i <= 10;i++){  if(i == 5){   continue;  }  printf("i 的值 = %d\n",i); }  return 0;}

(这段代码的输出结果会输出出 i = 5 以外的值)

从输出结果可以看出,只有 i = 5 的值没有输出,这也就是说,当代码执行到 i == 5 的时候,会进行 continue 继续执行当前循环,从而跳过这次循环后面的代码,如下图所示。

图片

总结

这篇文章我主要和你聊了聊 C 语言中的函数,函数定义、函数返回值、参数以及程序控制流程中的三类循环的特点以及选型,最后又介绍了一下 break 和 continue 的作用。


Minio是一个开源的分布式文件存储系统,它基于 Golang 编写,虽然轻量,却拥有着不错的高性能,可以将图片、视频、音乐、pdf这些文件存储到多个主机,可以存储到多个Linux,或者多个Windows,或者多个Mac,Minio中存储最大文件可以达到5TB任何类型的文件都是支持的,主要应用在微服务系统中.


图片

一、准备机器


获取最新更新以及文章用到的软件包,请移步点击:查看更新

1、准备四台机器,(minio集群最少四台)。





192.168.223.131 minio-1192.168.223.128 minio-2192.168.223.129 minio-3192.168.223.130 minio-4

2、编辑hosts文件,将以上内容添加到hosts中


vim /etc/hosts

图片

部署(所有机器均执行)

以下的操作都需要在四台机器上执行

3、创建挂载磁盘路径


mkdir -p /data/minio_data/

4、挂载磁盘路径到文件系统

注意:需要将新建的目录挂在到对应的磁盘下,磁盘不挂载好,集群启动会报错,还需要注意的是挂载的文件系统至少要1G不然无法初始化导致集群报错

文件系统 容量 已用 可用 已用% 挂载点












[root@minio-1 minio]# df -h文件系统                                容量  已用  可用 已用% 挂载点devtmpfs                                470M     0  470M    0% /devtmpfs                                   487M     0  487M    0% /dev/shmtmpfs                                   487M  8.4M  478M    2% /runtmpfs                                   487M     0  487M    0% /sys/fs/cgroup/dev/mapper/centos_hadoop--master-root   47G   12G   36G   25% //dev/sda1                              1014M  240M  775M   24% /boottmpfs                                    98M     0   98M    0% /run/user/0tmpfs                                    98M   12K   98M    1% /run/user/42————————————————

5、将上面挂载磁盘路径挂载到相应的文件系统上


mount /dev/sda1 /data/minio_data/

6、查看挂载信息

图片

 7、创建minio目录


cd   /data/minio_data/

  8、下载安装包



wget http://dl.minio.org.cn/server/minio/release/linux-amd64/miniowget https://dl.min.io/client/mc/release/linux-amd64/mc

  9、赋执行权限(根据情况,这里赋全部权限)


chmod +x minio mc

  10、创建启动脚本,编辑run.sh文件


mkdir /data/minio_data && cd /data/minio_data

内容如下:







cat > run.sh <<EOF#!/bin/bashexport MINIO_ACCESS_KEY=minioexport MINIO_SECRET_KEY=Leo825#20210423/usr/local/bin/minio server --address=192.168.81.235:9000 http://192.168.81.235/data/minio_data/data1  http://192.168.81.236/data/minio_data/data1  http://192.168.81.237/data/minio_data/data1 http://192.168.81.234/data/minio_data/data1EOF

11、赋执行权限(根据情况,这里赋全部权限)


chmod 777 /data/minio_data/run.sh

12、创建启动服务,创建minio.service启动脚本


vim /usr/lib/systemd/system/minio.service


内容如下:



















[Unit]Description=Minio serviceDocumentation=https://docs.minio.io/

[Service]#安装包路径WorkingDirectory=/data/minio_data#启动命令路径ExecStart=/data/minio_data/run.sh

Restart=on-failureRestartSec=5

[Install]WantedBy=multi-user.target

复制代码

13、启动测试(所有机器执行)


复制代码

重新加载服务的配置文件


systemctl daemon-reload


启动minio服务


systemctl start minio


查看minio状态












systemctl status minio[root@minio-2 ~]# systemctl status minio● minio.service - Minio serviceLoaded: loaded (/usr/lib/systemd/system/minio.service; disabled; vendor preset: disabled)Active: active (running) since 日 2021-01-31 17:22:54 CST; 17s agoDocs: https://docs.minio.io/Main PID: 2036 (run.sh)Tasks: 8CGroup: /system.slice/minio.service├─2036 /bin/bash /data/minio_data/run.sh└─2039 /data/minio_data server http://192.168.223.232/data/minio_data/data1 http://192.168.223.233/data/minio_data/data1

关闭minio服务


systemctl stop minio

复制代码

14、访问地址

集群中的任何一台机器都可以访问:





http://192.168.223.132:9000/http://192.168.223.133:9000/http://192.168.223.134:9000/http://192.168.223.135:9000/

15、创建测试桶

图片

16、上传测试

图片

17、主机上可以查看到上传的文件


图片


二、nginx配置文件服务器访问

1、执行命令




mc  alias set minio http://192.168.223.132:9000/ minio Leo825#20210423 --api S3v4开启匿名访问mc policy set public minio/sy01

2、web页面开启匿名访问


图片

图片


 3、http访问,sy01是桶名称,方便浏览器访问。




























































upstream minio-server{  server 192.168.6.124:9000 weight=25 max_fails=2 fail_timeout=30s;  server 192.168.6.125:9000 weight=25 max_fails=2 fail_timeout=30s;  server 192.168.6.126:9000 weight=25 max_fails=2 fail_timeout=30s;  server 192.168.6.128:9000 weight=25 max_fails=2 fail_timeout=30s;}

server {  listen 8888;  server_name 192.168.6.120;

  #To allow special characters in headers  ignore_invalid_headers off;  # Allow any size file to be uploaded.  # Set to a value such as 1000m; to restrict file size to a specific value  client_max_body_size 0;  # To disable buffering  proxy_buffering off;

  location /sy01/ {     proxy_set_header X-Real-IP $remote_addr;     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;     proxy_set_header X-Forwarded-Proto $scheme;     proxy_set_header Host $http_host;

     proxy_connect_timeout 300;     # Default is HTTP/1, keepalive is only enabled in HTTP/1.1     proxy_http_version 1.1;     proxy_set_header Connection "";     chunked_transfer_encoding off;

     proxy_pass http://minio-server;   }

   location / {     proxy_set_header X-Real-IP $remote_addr;     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;     proxy_set_header X-Forwarded-Proto $scheme;     proxy_set_header Host $http_host;

     proxy_connect_timeout 300;     # Default is HTTP/1, keepalive is only enabled in HTTP/1.1     proxy_http_version 1.1;     proxy_set_header Connection "";     chunked_transfer_encoding off;

     proxy_pass http://minio-server; # If you are using docker-compose this would be the hostname i.e. minio     # Health Check endpoint might go here. See https://www.nginx.com/resources/wiki/modules/healthcheck/     # /minio/health/live;   }}

  4、https访问,minio是负载minio服务,sy01是桶名称,方便浏览器访问。
















































upstream minio-server{       server 192.168.6.124:9000 weight=25 max_fails=2 fail_timeout=30s;       server 192.168.6.125:9000 weight=25 max_fails=2 fail_timeout=30s;       server 192.168.6.126:9000 weight=25 max_fails=2 fail_timeout=30s;       server 192.168.6.128:9000 weight=25 max_fails=2 fail_timeout=30s;}

server {    listen 443 ssl;    server_name  192.168.6.120;

    ssl_certificate /etc/nginx/ssl/192.168.6.120.crt;    ssl_certificate_key /etc/nginx/ssl/192.168.6.120.key;

    location /sy01/ {         proxy_set_header X-Real-IP $remote_addr;         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;         proxy_set_header X-Forwarded-Proto $scheme;         proxy_set_header Host $http_host;

         proxy_connect_timeout 300;         proxy_http_version 1.1;         proxy_set_header Connection "";         chunked_transfer_encoding off;         proxy_pass http://minio-server;  }

  location /minio {     proxy_set_header X-Real-IP $remote_addr;     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;     proxy_set_header X-Forwarded-Proto $scheme;     proxy_set_header Host $http_host;

     proxy_connect_timeout 300;     proxy_http_version 1.1;     proxy_set_header Connection "";     chunked_transfer_encoding off;     proxy_pass http://minio-server;  }  }

或者换成tcp代理也可以实现,放在http层外,如果服务器在内网,访问在外网,中间经过了代理,或者隧道之内的,必须要要tcp,否则会http头部hred验证会失败。













upstream minio-server {        server 192.168.178.40:9000     max_fails=3 fail_timeout=30s;        server 192.168.178.41:9000     max_fails=3 fail_timeout=30s;        server 192.168.178.42:9000     max_fails=3 fail_timeout=30s;        server 192.168.178.43:9000     max_fails=3 fail_timeout=30s;}server {        listen 9000;        proxy_connect_timeout 2s;        proxy_timeout 900s;        proxy_pass minio-server;}


企业级架构ELKF集群


图片

服务器

A:10.10.1.3

B:10.10.1.4

C:10.10.1.5

客户端

D:10.10.1.6

下面将用ABC代表服务器了

ABC修改计算机名称




A服务器:hostnamectl set-hostname node1B服务器:hostnamectl set-hostname node2C服务器:hostnamectl set-hostname node3


ADCD更新源并安装java




curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repocurl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repoyum install lrzsz vim java-1.8.0-openjdk java-1.8.0-openjdk-devel-y   java –version -y


ABC都切换到 


cd /usr/local/src/


吧文件下面文件都拷贝到上面的这个目录







apache-zookeeper-3.6.0-bin.tar.gzelasticsearch-7.13.1-x86_64.rpmfilebeat-7.13.1-x86_64.rpmkafka_2.12-2.5.0.tgzkibana-7.13.1-x86_64.rpmlogstash-7.13.1-x86_64.rpm


图片


ABC都要进行更新源,和安装java




curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repocurl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repoyum install lrzsz vim java-1.8.0-openjdk java-1.8.0-openjdk-devel-y   java –version -y


ABC安装ES集群服务,进行连接



cd /usr/local/src/yum localinstall elasticsearch-7.13.1-x86_64.rpm -y


 JVM的内存限制更改,根据实际环境调整,更具服务器内存修改,如果服务器8G,这里可以改为6G,给服务器预留1-2G即可



vi /etc/elasticsearch/jvm.options -Xms4g 改为 -Xmx2g





ES集群实战注意

  集群交互是使用证书交互

  搭建集群前需要先创建证书文件

ES集群交互CA证书创建  A服务器上


/usr/share/elasticsearch/bin/elasticsearch-certutil ca           #一直回车


查看证书位置


ll -rht /usr/share/elasticsearch/elastic-stack-ca.p12


创建证书


/usr/share/elasticsearch/bin/elasticsearch-certutil cert --ca /usr/share/elasticsearch/elastic-stack-ca.p12           #一直回车


查看证书



ll -rht /usr/share/elasticsearch/elastic-certificates.p12cp /usr/share/elasticsearch/elastic-certificates.p12 /etc/elasticsearch/elastic-certificates.p12


证书要求权限修改,不然集群搭建失败



chmod 600 /etc/elasticsearch/elastic-certificates.p12chown elasticsearch:elasticsearch /etc/elasticsearch/elastic-certificates.p12


吧加密ca证书拷贝到BC服务器



scp /etc/elasticsearch/elastic-certificates.p12 10.10.1.4:/etc/elasticsearch/scp /etc/elasticsearch/elastic-certificates.p12 10.10.1.5:/etc/elasticsearch/


图片


ABC服务器对比MD5值是否一致


md5sum /etc/elasticsearch/elastic-certificates.p12


图片


B服务器和C服务器都要执行



chmod 600 /etc/elasticsearch/elastic-certificates.p12chown elasticsearch:elasticsearch /etc/elasticsearch/elastic-certificates.p12


ADC服务器都要进行修改


















vi /etc/elasticsearch/elasticsearch.ymlcluster.name: maixiaolunnode.name: node1   #服务器A就改为nede1,服务器B改为node2,服务器C改为node3node.master: truenode.data: truepath.data: /var/lib/elasticsearchpath.logs: /var/log/elasticsearchnetwork.host: 0.0.0.0http.port: 9200discovery.seed_hosts: ["10.10.1.3", "10.10.1.4", "10.10.1.5"]cluster.initial_master_nodes: ["10.10.1.3", "10.10.1.4", "10.10.1.5"]xpack.security.enabled: truexpack.monitoring.enabled: truexpack.security.transport.ssl.enabled: truexpack.security.transport.ssl.verification_mode: certificatexpack.security.transport.ssl.keystore.path: /etc/elasticsearch/elastic-certificates.p12xpack.security.transport.ssl.truststore.path: /etc/elasticsearch/elastic-certificates.p12


ABC防火墙要放通端口









firewall-cmd --zone=public --add-port=9300/tcp --permanentfirewall-cmd --zone=public --add-port=9300/udp --permanentfirewall-cmd --zone=public --add-port=9200/tcp --permanentfirewall-cmd --zone=public --add-port=9200/udp --permanentfirewall-cmd --zone=public --add-port=5601/tcp --permanentfirewall-cmd --zone=public --add-port=5601/udp --permanentfirewall-cmd --zone=public --add-port=80/tcp --permanentfirewall-cmd --zone=public --add-port=3000/tcp --permanent

重新载入


firewall-cmd --reload


图片


图片


图片


ABC服务器顺序启动ES服务



systemctl enable elasticsearchsystemctl restart elasticsearch


继续看第一台服务器日志


图片


出现valid,表示成功了

接下来是查看3台集群中间的互动



yum -y install net-tools  安装 netstatnetstat -anp |grep 10.0.0.20:9300   查看集群之间的交互


检查9300端口连接情况


图片


确认集群中所有es的日志正常再设置密码maixiaolun..123

在主服务器(服务器A,想把那个当主服务器就在那个服务器上运行)上运行

ES设置密码/usr/share/elasticsearch/bin/elasticsearch-setup-passwords interactive


图片


ES设置随机密码/usr/share/elasticsearch/bin/elasticsearch-setup-passwords auto

验证集群是否成功,标记为*的为master节点。网页访问或者curl访问

http://10.10.1.3:9200    账号:elastic   密码:刚刚设置的

http://10.10.1.3:9200/_cat/nodes?v    //查看节点信息  带*的就是主的


图片


http://xxx:9200/_cat/indices?v

命令方式查看 输入账号密码

curl -u elastic:maixiaolun..123 http://10.10.1.3:9200/_cat/nodes?v


图片


B服务器安装Kibana (因为是前端,蹦了就蹦了,不要紧,所有不需要每个地方都搭建来)

B服务器安装



cd /usr/local/src/yum localinstall kibana-7.13.1-x86_64.rpm -y


图片


Kibana配置连接ES集群








vi /etc/kibana/kibana.ymlserver.port: 5601server.host: "0.0.0.0"elasticsearch.hosts: ["http://10.10.1.3:9200", "http://10.10.1.4:9200", "http://10.10.1.5:9200"]elasticsearch.username: "elastic"elasticsearch.password: "maixiaolun..123"logging.dest: /tmp/kibana.log



B服务器Kibana的启动和访问



systemctl enable kibanasystemctl restart kibana



Kibana监控开启


netstat -tulnp


图片


http://10.10.1.3:5601

账号:elastic

密码:maixiaolun..123


BC服务器安装logstash日志分析 (就是蹦了一台也不要紧,此做法为高可用做法,安装一台的话也是可以的)



cd /usr/local/src/yum localinstall logstash-7.13.1-x86_64.rpm -y


图片


内存配置jvm.options 1g,根据自己实际情况改


vi /etc/logstash/jvm.options

配置logstash配置文件



































vi /etc/logstash/conf.d/logstash.conf#监听5044传来的日志input {   beats {   host => '0.0.0.0'   port => 5044 }}#这里是正则表达式,分析日志filter {grok {match => {"message" => '%{IP:remote_addr} - (%{WORD:remote_user}|-) [%{HTTPDATE:time_local}] "%{WORD:method} %{NOTSPACE:request} HTTP/%{NUMBER}" %{NUMBER:status} %{NUMBER:body_bytes_sent} %{QS} %{QS:http_user_agent}'}remove_field => ["message"]}date {match => ["time_local", "dd/MMM/yyyy:HH:mm:ss Z"]target => "@timestamp"}}

#发送给所有ES服务器output {elasticsearch {hosts => ["http://10.10.1.3:9200", "http://10.10.1.4:9200", "http://10.10.1.5:9200"]user => "elastic"password => "maixiaolun..123"index => "mxlnginx-%{+YYYY.MM.dd}"}}


启动Logstash,配置重载:kill -1 pid



systemctl restart logstashsystemctl enable logstash


等待启动,查看日志

/var/log/logstash   日志生成的地方


tail -f logstash-plain.log


图片


ZK集群部署

所有服务器都要进行安装







cd /usr/local/src/tar -zxvf apache-zookeeper-3.6.0-bin.tar.gzmv apache-zookeeper-3.6.0-bin /usr/local/zookeepermkdir -pv /usr/local/zookeeper/datacd /usr/local/zookeeper/confcp zoo_sample.cfg zoo.cfg

  zk集群至少需要三台机器

集群配置zoo.cfg 












vi zoo.cfgtickTime=2000initLimit=10syncLimit=5dataDir=/usr/local/zookeeper/dataclientPort=2181autopurge.snapRetainCount=3autopurge.purgeInterval=1server.1=10.10.1.3:2888:3888server.2=10.10.1.4:2888:3888server.3=10.10.1.5:2888:3888


更改zk集群的id


vi /usr/local/zookeeper/data/myid

第一台就填写1

第二台就填写2

第三台就填写3


图片


图片

分别为1 2 3

systemctl管理











vi /usr/lib/systemd/system/zookeeper.service[Unit]Description=zookeeperAfter=network.target[Service]Type=forkingExecStart=/usr/local/zookeeper/bin/zkServer.sh startUser=root[Install]WantedBy=multi-user.target


启动zk



systemctl enable zookeepersystemctl restart zookeeper


启动zk集群查看状态




cd /usr/local/zookeeper/bin/./zkServer.sh start./zkServer.sh status


图片

报错的话,检查防火墙。

正确显示为下


图片

Kafka集群部署

下载地址:http://kafka.apache.org/downloads

安装



cd /usr/local/src/tar -zxvf kafka_2.12-2.5.0.tgz -C /usr/local/

  Jvm内存修改/usr/local/kafka_2.12-2.5.0/bin/kafka-server-start.sh,根据实际情况修

改,每台服务器都要改


图片


修改Kafka配置server.properties



cd /usr/local/kafka_2.12-2.5.0/config/vi server.properties


图片













broker.id=0listeners=PLAINTEXT://xxx:9092 log.retention.hours=1  #根据实际情况修改  日志保留时间zookeeper.connect=xxx:2181,xxx:2181,xxx:2181zookeeper.connection.timeout.ms=18000 改为 zookeeper.connection.timeout.ms=60000拷贝到其他服务器上scp server.properties 10.0.0.21:/usr/local/kafka_2.12-2.5.0/config/然后在新的服务器上vi /usr/local/kafka_2.12-2.5.0/config/server.properties改listeners=PLAINTEXT://xxx:9092 对应当前服务器的ipbroker.id=0 改为broker.id=1

如果启动的时候报如下错误,是因为链接超时,吧60000改为 1200000也就是2分钟


图片


Kafka使用systemctl管理











vi /usr/lib/systemd/system/kafka.service[Unit]Description=kafkaAfter=network.target[Service]Type=simpleExecStart=/usr/local/kafka_2.12-2.5.0/bin/kafka-server-start.sh /usr/local/kafka_2.12-2.5.0/config/server.propertiesUser=root[Install]WantedBy=multi-user.target

Kafka启动



systemctl enable kafka systemctl restart kafka


查看kafka日志



cd /usr/local/kafka_2.12-2.5.0/logstail -f server.log


图片


创建topic,创建成功说明kafka集群搭建成功

--replication-factor 3 代表3台服务器



cd /usr/local/kafka_2.12-2.5.0/bin/./kafka-topics.sh --create --zookeeper 10.10.1.4:2181 --replication-factor 3 --partitions 1 --to


图片


logstash获取kafka数据 


vi /etc/logstash/conf.d/logstash.conf


#获取kafka的日志






















































input { kafka {   bootstrap_servers => "10.10.1.3:9092,10.10.1.4:9092,10.10.1.5:9092"   topics => ["mxlkafka"]   group_id => "mxlgroup"   codec => "json" }}





filter { mutate {   remove_field => ["agent","ecs","log","input","[host][os]"] }}



#正则表达式分析日志filter {grok {match => {"message" => '%{IP:remote_addr} - (%{WORD:remote_user}|-) [%{HTTPDATE:time_local}] "%{WORD:method} %{NOTSPACE:request} HTTP/%{NUMBER}" %{NUMBER:status} %{NUMBER:body_bytes_sent} %{QS} %{QS:http_user_agent}'}remove_field => ["message"]}date {match => ["time_local", "dd/MMM/yyyy:HH:mm:ss Z"]target => "@timestamp"}}



#吧分析过后的日志发送给ESoutput {elasticsearch {hosts => ["http://10.10.1.3:9200", "http://10.10.1.4:9200", "http://10.10.1.5:9200"]user => "elastic"password => "maixiaolun..123"index => "mxlnginx-%{+YYYY.MM.dd}"     #这里代表日志的名字,不通服务器可以写不通的名字}}

重载



ps auxfww | grep logstashkill -1 804


图片


看日志

cd /var/log/logstash/

tail -f logstash-plain.log


图片


测试环境是否正常

效果同ELFK,但架构做了优化

Kafka查看队列信息,验证新架构是否生效




cd /usr/local/kafka_2.12-2.5.0/bin/./kafka-consumer-groups.sh --bootstrap-server 10.10.1.4:9092 --list./kafka-consumer-groups.sh --bootstrap-server 10.10.1.4:9092 --group mxlgroup -describe


  LOG-END-OFFSET不断增大,LAG不堆积说明架构生效


图片


LOG-END-OFFSET 一直增加就代表kafka在消费

LAG 一直在堆积

Logstash扩展

  配置保持一致,启动即可




客户端D进行安装nginx进行测试数据是否传入服务器集群

安装nginx做测试,如果有其他服务可以直接测试



yum install nginx -ysystemctl start nginx


安装filebeat,用于发送日志到服务器



cd /usr/local/src/yum localinstall filebeat-7.13.1-x86_64.rpm -y




配置filebeat进行监控nginx



















vi /etc/filebeat/filebeat.ymlfilebeat.inputs:- type: log  tail_files: true  backoff: "1s"  paths:     - /var/log/nginx/access.log

processors:- drop_fields:   fields: ["agent","ecs","log","input"]

output:  kafka:    hosts: ["10.10.1.3:9092", "10.10.1.4:9092", "10.10.1.5:9092"]    topic: mxlkafka   #这里代表日志的名字,不通服务器可以写不通的名字


启动filebeat



systemctl start filebeatsystemctl enable filebeat


登入B服务器网页

http://10.10.1.4:5601

账号:elastic

密码:maixiaolun..123


打开Stack Management


图片



添加日志数据 Index Patterns


图片


添加


图片



输入刚刚设置的名字 mxlnginx* 点击下一步

图片


选择@timestamp  下一步


图片


这时候就能看到添加的内容了



图片



访问客户端地址nginx页面 10.10.1.6  多刷新几次


图片

就可以去看数据了


图片



看到日志已经上来了



图片


读者可在公众号后台回复关键字 ELKF集群7搭建 获取二进制安装包


1、日志

在Kubernetes或者说Docker中都可以查看容器的日志,但如果直接通过命令行的方式去看的话会很麻烦,要不断的去敲命令,去找容器名称,很不方便操作!

在没有使用容器的项目中,我们也知道有ELK这一套组合拳是专门用来操作日志的,那K8S肯定也是可以用它们来进行日志收集、查看的。

1.1 日志查看

1.1.1 命令方式

1.1.1.1 docker

  • docker ps 查看 container id

    image.png

  • docker logs container_id 查看日志

    image.png

1.1.1.2 Kubernetes命令

  • kubectl 命令查看

    image.png

    • kubectl logs -f <pod-name> [-c <container-name>]

  • Pod日志

    image.png

    • kubectl describe pod <pod-name>

      kubectl describe除了能够查看pod的日志信息,还能查看比如Node、RC、Service、Namespace等信息。

      注意 :要是想查看指定命名空间之下的,可以-n=namespace

  • 组件服务级别

    比如kube-apiserver、kube-schedule、kubelet、kube-proxy、kube-controller-manager等都可以使用journalctl进行查看

    如果K8S中出现问题了,一般就可以通过这些命令来查看是不是组件出异常了

    image.png

1.1.2 ELK

Elasticsearch搜索引擎与名为Logstash的数据收集和日志解析引擎以及名为Kibana的分析和可视化平台一起开发。这三个产品被设计成一个集成解决方案,称为 Elastic Stack(ELK Stack)

日志采集可以有多种方式选择,ELK中的Logstash可以由其他具有相同功能的多个组件替换,如Logpilot、fluentd等,本章节我们采用 Logpilot 进行演示。

1.1.2.1 结构图

  • Pod中的日志通过mount挂载到宿主机中的某个目录

  • Logpilot采集这个目录下日志之后交给Elasticsearch去做搜索引擎的规则

  • 最后通过Kibana做可视化的展示

image.png

1.1.2.2 部署ELK

1.1.2.2.1 部署LogPilot
  • 准备YAML文件

    由于环境被我弄坏了,我这边重新安装了K8S集群,并且采用了最新版的1.18.2

    下面的yaml文件可能会不兼容旧版本,如果不兼容,大家网上搜一份修改下

    apiVersion: apps/v1kind: DaemonSet                   # 日志采集需要在所有节点都运行,所以采用DaemonSet资源metadata:
      name: log-pilot  namespace: kube-system  labels:
        k8s-app: log-pilot    kubernetes.io/cluster-service: "true"spec:
      selector: 
        matchLabels: 
          k8s-app: log-es      kubernetes.io/cluster-service: "true"
          version: v1.22  template:
        metadata:
          labels:
            k8s-app: log-es        kubernetes.io/cluster-service: "true"
            version: v1.22    spec:
          tolerations:                # 这里的配置表示允许将资源部署到Master节点中
          - key: node-role.kubernetes.io/master        effect: NoSchedule      containers:
          - name: log-pilot        image: registry.cn-hangzhou.aliyuncs.com/acs/log-pilot:0.9.5-filebeat        resources:                # 资源限制
              limits: 
                memory: 200Mi          requests:
                cpu: 100m            memory: 200Mi        env:                  # 定义与Elasticsearch通信的一些环境变量
              - name: "FILEBEAT_OUTPUT"
                value: "elasticsearch"
              - name: "ELASTICSEARCH_HOST"
                value: "elasticsearch-api"
              - name: "ELASTICSEARCH_PORT"
                value: "9200"
              - name: "ELASTICSEARCH_USER"
                value: "elastic"
              - name: "ELASTICSEARCH_PASSWORD"
                value: "elastic"
            volumeMounts:         # 挂载日志目录
            - name: sock          mountPath: /var/run/docker.sock        - name: root          mountPath: /host          readOnly: true
            - name: varlib          mountPath: /var/lib/filebeat        - name: varlog          mountPath: /var/log/filebeat        securityContext:
              capabilities:
                add:
                - SYS_ADMIN      terminationGracePeriodSeconds: 30
          volumes:
          - name: sock        hostPath:
              path: /var/run/docker.sock      - name: root        hostPath:
              path: /      - name: varlib        hostPath:
              path: /var/lib/filebeat          type: DirectoryOrCreate      - name: varlog        hostPath:
              path: /var/log/filebeat          type: DirectoryOrCreate
    • log-pilot.yaml

  • 创建资源

    [root@master-kubeadm-k8s log]# kubectl apply -f log-pilot.yamldaemonset.extensions/log-pilot created
  • 查看资源

    [root@master-kubeadm-k8s log]# kubectl get pods -n kube-system -o wide | grep loglog-pilot-8f4nv                              1/1     Running            0          2m4s   192.168.221.88    worker02-kubeadm-k8s   <none>           <none>log-pilot-h25fc                              1/1     Running            0          2m4s   192.168.16.250    master-kubeadm-k8s     <none>           <none>
        [root@master-kubeadm-k8s log]# kubectl get daemonset -n kube-systemNAME          DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR                 AGEcalico-node   3         3         3       3            3           beta.kubernetes.io/os=linux   41d
    kube-proxy    3         3         3       3            3           <none>                        41d
    log-pilot     2         2         2       2            2           <none>                        26s
1.1.2.2.2 部署Elasticsearch
  • 准备YAML文件

    注意这里的资源要求还是比较高的,自己注意下系统资源是否够用

    apiVersion: v1kind: Service             # 这里的service是为外部访问ElasticSearch提供metadata:
      name: elasticsearch-api # 这里的名称要与logPilot中的ElasticHost环境变量一致
      namespace: kube-system  
      labels:
        name: elasticsearchspec:
      selector:
        app: es  ports:
      - name: transport    port: 9200
        protocol: TCP---apiVersion: v1kind: Service             # # 这里的service是为ElasticSearch集群之间通信提供的metadata:
      name: elasticsearch-discovery  namespace: kube-system  labels:
        name: elasticsearchspec:
      selector:
        app: es  ports:
      - name: transport    port: 9300
        protocol: TCP---apiVersion: apps/v1kind: StatefulSet         # 希望Elasticsearch是有序启动的,所以使用StatefulSetmetadata:
      name: elasticsearch  namespace: kube-system  labels:
        kubernetes.io/cluster-service: "true"spec:
      replicas: 3
      serviceName: "elasticsearch-service"
      selector:
        matchLabels:
          app: es  template:
        metadata:
          labels:
            app: es    spec:
          tolerations:            # 同样elasticsearch也部署到Master节点中
          - effect: NoSchedule        key: node-role.kubernetes.io/master      initContainers:     # 这里是指在创建容器之前要先进行初始化操作
          - name: init-sysctl        image: busybox:1.27
            command:
            - sysctl        - -w        - vm.max_map_count=262144        securityContext:
              privileged: true
          containers:
          - name: elasticsearch        image: registry.cn-hangzhou.aliyuncs.com/log-monitor/elasticsearch:v5.5.1        ports:
            - containerPort: 9200
              protocol: TCP        - containerPort: 9300
              protocol: TCP        securityContext:
              capabilities:
                add:
                  - IPC_LOCK              - SYS_RESOURCE        resources:
              limits:
                memory: 4000Mi          requests:
                cpu: 100m            memory: 2000Mi        env:
              - name: "http.host"
                value: "0.0.0.0"
              - name: "network.host"
                value: "_eth0_"
              - name: "cluster.name"
                value: "docker-cluster"
              - name: "bootstrap.memory_lock"
                value: "false"
              - name: "discovery.zen.ping.unicast.hosts"
                value: "elasticsearch-discovery"
              - name: "discovery.zen.ping.unicast.hosts.resolve_timeout"
                value: "10s"
              - name: "discovery.zen.ping_timeout"
                value: "6s"
              - name: "discovery.zen.minimum_master_nodes"
                value: "2"
              - name: "discovery.zen.fd.ping_interval"
                value: "2s"
              - name: "discovery.zen.no_master_block"
                value: "write"
              - name: "gateway.expected_nodes"
                value: "2"
              - name: "gateway.expected_master_nodes"
                value: "1"
              - name: "transport.tcp.connect_timeout"
                value: "60s"
              - name: "ES_JAVA_OPTS"
                value: "-Xms2g -Xmx2g"
            livenessProbe:                    # 健康检查
              tcpSocket:
                port: transport          initialDelaySeconds: 20
              periodSeconds: 10
            volumeMounts:
            - name: es-data          mountPath: /data      terminationGracePeriodSeconds: 30
          volumes:
          - name: es-data        hostPath:
              path: /es-data
    • elasticsearch.yaml

  • 创建资源

    [root@master-kubeadm-k8s log]# kubectl apply -f elasticsearch.yamlservice/elasticsearch-api created
    service/elasticsearch-discovery created
    statefulset.apps/elasticsearch created
  • 查看资源

    # Pod会进行有序的创建[root@master-kubeadm-k8s log]# kubectl get pods -n kube-system -o wide | grep elasticelasticsearch-0                              1/1     Running           0          4m36s   10.244.221.69    worker02- kubeadm-k8s   <none>           <none>elasticsearch-1                              1/1     Running           0          4m33s   10.244.14.4      worker01-kubeadm-k8s   <none>           <none>elasticsearch-2                              0/1     PodInitializing   0          101s    10.244.16.194    master-kubeadm-k8s     <none>           <none>[root@master-kubeadm-k8s log]# kubectl get svc -n kube-system -o wide | grep elasticelasticsearch-api         ClusterIP   10.104.144.183   <none>        9200/TCP                 5m2s   app=es
    elasticsearch-discovery   ClusterIP   10.109.137.36    <none>        9300/TCP                 5m2s   app=es[root@master-kubeadm-k8s log]# kubectl get statefulset -n kube-systemNAME            READY   AGEelasticsearch   3/3     6m14s
1.1.2.2.3 部署Kibana

kibana主要是对外提供访问的,所以这边需要配置Service和Ingress
前提:要有Ingress Controller的支持,比如Nginx Controller

  • 准备YAML文件

    • kibana.yaml

      # DeploymentapiVersion: apps/v1kind: Deploymentmetadata:
        name: kibana  namespace: kube-system  labels:
          component: kibanaspec:
        replicas: 1
        selector:
          matchLabels:
           component: kibana  template:
          metadata:
            labels:
              component: kibana    spec:
            containers:
            - name: kibana        image: registry.cn-hangzhou.aliyuncs.com/log-monitor/kibana:v5.5.1        env:
              - name: CLUSTER_NAME          value: docker-cluster        - name: ELASTICSEARCH_URL   # elasticsearch 地址
                value: http://elasticsearch-api:9200/        resources:
                limits:
                  cpu: 1000m          requests:
                  cpu: 100m        ports:
              - containerPort: 5601
                name: http---# ServiceapiVersion: v1kind: Servicemetadata:
        name: kibana  namespace: kube-system  labels:
          component: kibanaspec:
        selector:
          component: kibana  ports:
        - name: http    port: 80
          targetPort: http---# IngressapiVersion: extensions/v1beta1kind: Ingressmetadata:
        name: kibana  namespace: kube-systemspec:
        rules:
        - host: log.k8s.sunny.com     # 本地hosts配置域名
          http:
            paths:
            - path: /        backend:
                serviceName: kibana          servicePort: 80
  • 创建资源

    [root@master-kubeadm-k8s log]# kubectl apply -f kibana.yamldeployment.apps/kibana created
    service/kibana created
    ingress.extensions/kibana created
  • 查看资源

    [root@master-kubeadm-k8s log]# kubectl get pods -n kube-system | grep kibanakibana-8747dff7d-l627g                       1/1     Running   0          2m2s[root@master-kubeadm-k8s log]# kubectl get svc  -n kube-system | grep kibanakibana                    ClusterIP   10.109.177.214   <none>        80/TCP                   2m40s[root@master-kubeadm-k8s log]# kubectl get ingress  -n kube-system | grep kibanakibana   <none>   log.k8s.sunny.com             80      2m43s
  • 测试

image.png

2、监控

2.1 Prometheus简介

这里的监控是指监控K8S集群的健康状态,包括节点、K8S组件以及Pod都会监控。

Prometheus 是一个开源的监控和警报系统,它直接从目标主机上运行的代理程序中抓取指标,并将收集的样本集中存储在其服务器上。

2016 年 Prometheus 成为继 Kubernetes 之后,成为 CNCF (Cloud Native Computing Foundation)中的第二个项目成员。

2.1.2 主要功能

  • 多维 数据模型(时序由 metric 名字和 k/v 的 labels 构成)。

  • 灵活的查询语句(PromQL)。

  • 无依赖存储,支持 local 和 remote 不同模型。

  • 采用 http 协议,使用 pull 模式,拉取数据,简单易懂。

  • 监控目标,可以采用服务发现或静态配置的方式。

  • 支持多种统计数据模型,图形化友好。

2.1.3 Prometheus架构

image.png

从这个架构图,也可以看出 Prometheus 的主要模块包含:Server、Exporters、Pushgateway、PromQL、Alertmanager、WebUI 等。

它大致使用逻辑是这样:

  • Prometheus server 定期从静态配置的 target 或者服务发现的 target 拉取数据。

  • 当新拉取的数据大于配置内存缓存区的时候,Prometheus 会将数据持久化到磁盘(如果使用 remote storage 将持久化到云端)。

  • Prometheus 可以配置 rule,然后定时查询数据,当条件触发的时候,会将 alert 推送到配置的 Alertmanager。

  • Alertmanager 收到警告的时候,可以根据配置,聚合、去重、降噪,最后发送警告。

  • 可以使用 API、Prometheus Console 或者 Grafana 查询和聚合数据。

2.1.4 Prometheus知识普及

  • 支持pull、push数据添加方式

  • 支持k8s服务发现

  • 提供查询语言PromQL

  • 时序(time series)是由名字(Metric)以及一组key/value标签定义的数据类型

2.2 数据采集

  • 服务器数据

    • 在每一个节点中部署工具:Node-Exporter

  • K8S组件数据

    • IP:2379/metrics 拿到ETCD数据

    • IP:6443/metrics 拿到apiServer数据

    • IP:10252/metrics 拿到controller manager数据

    • IP:10251/metrics 拿到scheduler数据

    • 访问集群中不同端口下的metrics接口即可拿到

    • 比如

  • 容器数据

    • 在kubelet中有个cAdvisor组件,默认就可以拿到容器的数据

2.2.1 服务器数据采集

Prometheus可以从Kubernetes集群的各个组件中采集数据,比如kubelet中自带的cAdvisor,api-server等,而node-export就是其中一种来源。

Exporter是Prometheus的一类数据采集组件的总称。它负责从目标处搜集数据,并将其转化为Prometheus支持的格式。

服务器的指标采集是通过 Node-Exporter 进行采集,比如服务器CPU、内存、磁盘、I/O等信息

image.png

2.2.1.1 部署NodeExporter

  • 准备YAML文件

    • namespace.yaml

      我们将监控的资源都放到这个命名空间下,方便管理

      apiVersion: v1kind: Namespacemetadata: 
        name: ns-monitor  labels:
          name: ns-monitor
    • node-exporter.yaml

      kind: DaemonSetapiVersion: apps/v1metadata: 
        labels:
          app: node-exporter  name: node-exporter  namespace: ns-monitorspec:
        revisionHistoryLimit: 10
        selector:
          matchLabels:
            app: node-exporter  template:
          metadata:
            labels:
              app: node-exporter    spec:
            containers:
              - name: node-exporter          image: prom/node-exporter:v0.16.0          ports:
                  - containerPort: 9100
                    protocol: TCP              name: http      hostNetwork: true
            hostPID: true
            tolerations:
              - effect: NoSchedule          operator: Exists---# 本来NodeExporter是不需要对外提供访问的,但我们这里一步一步来,先保证每一步都正确再往后进行kind: ServiceapiVersion: v1metadata:
        labels:
          app: node-exporter  name: node-exporter-service  namespace: ns-monitorspec:
        ports:
          - name: http      port: 9100
            nodePort: 31672
            protocol: TCP  type: NodePort  selector:
          app: node-exporter
  • 创建资源

    [root@master-kubeadm-k8s prometheus]# kubectl apply -f node-exporter.yamldaemonset.apps/node-exporter created
    service/node-exporter-service created
  • 查看资源

    [root@master-kubeadm-k8s prometheus]# kubectl get pods -n ns-monitorNAME                  READY   STATUS    RESTARTS   AGEnode-exporter-dsjbq   1/1     Running   0          2m32s
    node-exporter-mdnrj   1/1     Running   0          2m32s
    node-exporter-sxwxx   1/1     Running   0          2m32s[root@master-kubeadm-k8s prometheus]# kubectl get svc -n ns-monitorNAME                    TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGEnode-exporter-service   NodePort   10.109.226.6   <none>        9100:31672/TCP   2m46s
  • 测试

    image.png

2.2.1 部署Prometheus

  • 准备YAML文件

    • prometheus.yaml

      prometheus的yaml文件很值得学习,很多常用的资源类型这里都用到了

      apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata:
        name: prometheusrules:
        - apiGroups: [""] # "" indicates the core API group
          resources:
            - nodes      - nodes/proxy      - services      - endpoints      - pods    verbs:
            - get      - watch      - list  - apiGroups:
            - extensions    resources:
            - ingresses    verbs:
            - get      - watch      - list  - nonResourceURLs: ["/metrics"]
          verbs:
            - get---apiVersion: v1kind: ServiceAccountmetadata:
        name: prometheus  namespace: ns-monitor  labels:
          app: prometheus---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata:
        name: prometheussubjects:
        - kind: ServiceAccount    name: prometheus    namespace: ns-monitorroleRef:
        kind: ClusterRole  name: prometheus  apiGroup: rbac.authorization.k8s.io---apiVersion: v1kind: ConfigMapmetadata:
        name: prometheus-conf  namespace: ns-monitor  labels:
          app: prometheusdata:
        prometheus.yml: |-
          # my global config
          global:
            scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
            evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
            # scrape_timeout is set to the global default (10s).
      
          # Alertmanager configuration
          alerting:
            alertmanagers:
            - static_configs:
              - targets:
                # - alertmanager:9093
      
          # Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
          rule_files:
            # - "first_rules.yml"
            # - "second_rules.yml"
      
          # A scrape configuration containing exactly one endpoint to scrape:
          # Here it's Prometheus itself.
          scrape_configs:
            # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
            - job_name: 'prometheus'
      
              # metrics_path defaults to '/metrics'
              # scheme defaults to 'http'.
      
              static_configs:
                - targets: ['localhost:9090']
            - job_name: 'grafana'
              static_configs:
                - targets:
                    - 'grafana-service.ns-monitor:3000'
      
            - job_name: 'kubernetes-apiservers'
      
              kubernetes_sd_configs:
              - role: endpoints        # Default to scraping over https. If required, just disable this or change to
              # `http`.
              scheme: https        # This TLS & bearer token file config is used to connect to the actual scrape
              # endpoints for cluster components. This is separate to discovery auth
              # configuration because discovery & scraping are two separate concerns in
              # Prometheus. The discovery auth config is automatic if Prometheus runs inside
              # the cluster. Otherwise, more config options have to be provided within the
              # <kubernetes_sd_config>.
              tls_config:
                ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt          # If your node certificates are self-signed or use a different CA to the
                # master CA, then disable certificate verification below. Note that
                # certificate verification is an integral part of a secure infrastructure
                # so this should only be disabled in a controlled environment. You can
                # disable certificate verification by uncommenting the line below.
                #
                # insecure_skip_verify: true
              bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token        # Keep only the default/kubernetes service endpoints for the https port. This
              # will add targets for each API server which Kubernetes adds an endpoint to
              # the default/kubernetes service.
              relabel_configs:
              - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
                action: keep          regex: default;kubernetes;https      # Scrape config for nodes (kubelet).
            #
            # Rather than connecting directly to the node, the scrape is proxied though the
            # Kubernetes apiserver.  This means it will work if Prometheus is running out of
            # cluster, or can't connect to nodes for some other reason (e.g. because of
            # firewalling).
            - job_name: 'kubernetes-nodes'
      
              # Default to scraping over https. If required, just disable this or change to
              # `http`.
              scheme: https        # This TLS & bearer token file config is used to connect to the actual scrape
              # endpoints for cluster components. This is separate to discovery auth
              # configuration because discovery & scraping are two separate concerns in
              # Prometheus. The discovery auth config is automatic if Prometheus runs inside
              # the cluster. Otherwise, more config options have to be provided within the
              # <kubernetes_sd_config>.
              tls_config:
                ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt        bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token        kubernetes_sd_configs:
              - role: node        relabel_configs:
              - action: labelmap          regex: __meta_kubernetes_node_label_(.+)        - target_label: __address__          replacement: kubernetes.default.svc:443
              - source_labels: [__meta_kubernetes_node_name]
                regex: (.+)          target_label: __metrics_path__          replacement: /api/v1/nodes/${1}/proxy/metrics      # Scrape config for Kubelet cAdvisor.
            #
            # This is required for Kubernetes 1.7.3 and later, where cAdvisor metrics
            # (those whose names begin with 'container_') have been removed from the
            # Kubelet metrics endpoint.  This job scrapes the cAdvisor endpoint to
            # retrieve those metrics.
            #
            # In Kubernetes 1.7.0-1.7.2, these metrics are only exposed on the cAdvisor
            # HTTP endpoint; use "replacement: /api/v1/nodes/${1}:4194/proxy/metrics"
            # in that case (and ensure cAdvisor's HTTP server hasn't been disabled with
            # the --cadvisor-port=0 Kubelet flag).
            #
            # This job is not necessary and should be removed in Kubernetes 1.6 and
            # earlier versions, or it will cause the metrics to be scraped twice.
            - job_name: 'kubernetes-cadvisor'
      
              # Default to scraping over https. If required, just disable this or change to
              # `http`.
              scheme: https        # This TLS & bearer token file config is used to connect to the actual scrape
              # endpoints for cluster components. This is separate to discovery auth
              # configuration because discovery & scraping are two separate concerns in
              # Prometheus. The discovery auth config is automatic if Prometheus runs inside
              # the cluster. Otherwise, more config options have to be provided within the
              # <kubernetes_sd_config>.
              tls_config:
                ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt        bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token        kubernetes_sd_configs:
              - role: node        relabel_configs:
              - action: labelmap          regex: __meta_kubernetes_node_label_(.+)        - target_label: __address__          replacement: kubernetes.default.svc:443
              - source_labels: [__meta_kubernetes_node_name]
                regex: (.+)          target_label: __metrics_path__          replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor      # Scrape config for service endpoints.
            #
            # The relabeling allows the actual service scrape endpoint to be configured
            # via the following annotations:
            #
            # * `prometheus.io/scrape`: Only scrape services that have a value of `true`
            # * `prometheus.io/scheme`: If the metrics endpoint is secured then you will need
            # to set this to `https` & most likely set the `tls_config` of the scrape config.
            # * `prometheus.io/path`: If the metrics path is not `/metrics` override this.
            # * `prometheus.io/port`: If the metrics are exposed on a different port to the
            # service then set this appropriately.
            - job_name: 'kubernetes-service-endpoints'
      
              kubernetes_sd_configs:
              - role: endpoints        relabel_configs:
              - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
                action: keep          regex: true
              - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
                action: replace          target_label: __scheme__          regex: (https?)        - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
                action: replace          target_label: __metrics_path__          regex: (.+)        - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
                action: replace          target_label: __address__          regex: ([^:]+)(?::\d+)?;(\d+)          replacement: $1:$2        - action: labelmap          regex: __meta_kubernetes_service_label_(.+)        - source_labels: [__meta_kubernetes_namespace]
                action: replace          target_label: kubernetes_namespace        - source_labels: [__meta_kubernetes_service_name]
                action: replace          target_label: kubernetes_name      # Example scrape config for probing services via the Blackbox Exporter.
            #
            # The relabeling allows the actual service scrape endpoint to be configured
            # via the following annotations:
            #
            # * `prometheus.io/probe`: Only probe services that have a value of `true`
            - job_name: 'kubernetes-services'
      
              metrics_path: /probe        params:
                module: [http_2xx]
      
              kubernetes_sd_configs:
              - role: service        relabel_configs:
              - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_probe]
                action: keep          regex: true
              - source_labels: [__address__]
                target_label: __param_target        - target_label: __address__          replacement: blackbox-exporter.example.com:9115
              - source_labels: [__param_target]
                target_label: instance        - action: labelmap          regex: __meta_kubernetes_service_label_(.+)        - source_labels: [__meta_kubernetes_namespace]
                target_label: kubernetes_namespace        - source_labels: [__meta_kubernetes_service_name]
                target_label: kubernetes_name      # Example scrape config for probing ingresses via the Blackbox Exporter.
            #
            # The relabeling allows the actual ingress scrape endpoint to be configured
            # via the following annotations:
            #
            # * `prometheus.io/probe`: Only probe services that have a value of `true`
            - job_name: 'kubernetes-ingresses'
      
              metrics_path: /probe        params:
                module: [http_2xx]
      
              kubernetes_sd_configs:
                - role: ingress        relabel_configs:
                - source_labels: [__meta_kubernetes_ingress_annotation_prometheus_io_probe]
                  action: keep            regex: true
                - source_labels: [__meta_kubernetes_ingress_scheme,__address__,__meta_kubernetes_ingress_path]
                  regex: (.+);(.+);(.+)            replacement: ${1}://${2}${3}
                  target_label: __param_target          - target_label: __address__            replacement: blackbox-exporter.example.com:9115
                - source_labels: [__param_target]
                  target_label: instance          - action: labelmap            regex: __meta_kubernetes_ingress_label_(.+)          - source_labels: [__meta_kubernetes_namespace]
                  target_label: kubernetes_namespace          - source_labels: [__meta_kubernetes_ingress_name]
                  target_label: kubernetes_name      # Example scrape config for pods
            #
            # The relabeling allows the actual pod scrape endpoint to be configured via the
            # following annotations:
            #
            # * `prometheus.io/scrape`: Only scrape pods that have a value of `true`
            # * `prometheus.io/path`: If the metrics path is not `/metrics` override this.
            # * `prometheus.io/port`: Scrape the pod on the indicated port instead of the
            # pod's declared ports (default is a port-free target if none are declared).
            - job_name: 'kubernetes-pods'
      
              kubernetes_sd_configs:
              - role: pod        relabel_configs:
              - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
                action: keep          regex: true
              - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
                action: replace          target_label: __metrics_path__          regex: (.+)        - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
                action: replace          regex: ([^:]+)(?::\d+)?;(\d+)          replacement: $1:$2          target_label: __address__        - action: labelmap          regex: __meta_kubernetes_pod_label_(.+)        - source_labels: [__meta_kubernetes_namespace]
                action: replace          target_label: kubernetes_namespace        - source_labels: [__meta_kubernetes_pod_name]
                action: replace          target_label: kubernetes_pod_name---apiVersion: v1kind: ConfigMapmetadata:
        name: prometheus-rules  namespace: ns-monitor  labels:
          app: prometheusdata:
        cpu-usage.rule: |
          groups:
            - name: NodeCPUUsage
              rules:
                - alert: NodeCPUUsage
                  expr: (100 - (avg by (instance) (irate(node_cpu{name="node-exporter",mode="idle"}[5m])) * 100)) > 75
                  for: 2m
                  labels:
                    severity: "page"
                  annotations:
                    summary: "{{$labels.instance}}: High CPU usage detected"
                    description: "{{$labels.instance}}: CPU usage is above 75% (current value is: {{ $value }})"---apiVersion: v1kind: PersistentVolumemetadata:
        name: "prometheus-data-pv"
        labels:
          name: prometheus-data-pv    release: stablespec:
        capacity:
          storage: 5Gi  accessModes:
          - ReadWriteOnce  persistentVolumeReclaimPolicy: Recycle  nfs:
          path: /nfs/data/prometheus  # 定义持久化存储目录
          server: 192.168.50.111      # NFS服务器---apiVersion: v1kind: PersistentVolumeClaimmetadata:
        name: prometheus-data-pvc  namespace: ns-monitorspec:
        accessModes:
          - ReadWriteOnce  resources:
          requests:
            storage: 5Gi  selector:
          matchLabels:
            name: prometheus-data-pv      release: stable---kind: DeploymentapiVersion: apps/v1metadata:
        labels:
          app: prometheus  name: prometheus  namespace: ns-monitorspec:
        replicas: 1
        revisionHistoryLimit: 10
        selector:
          matchLabels:
            app: prometheus  template:
          metadata:
            labels:
              app: prometheus    spec:
            serviceAccountName: prometheus      securityContext:
              runAsUser: 0
            containers:
              - name: prometheus          image: prom/prometheus:latest          imagePullPolicy: IfNotPresent          volumeMounts:
                  - mountPath: /prometheus              name: prometheus-data-volume            - mountPath: /etc/prometheus/prometheus.yml              name: prometheus-conf-volume              subPath: prometheus.yml            - mountPath: /etc/prometheus/rules              name: prometheus-rules-volume          ports:
                  - containerPort: 9090
                    protocol: TCP      volumes:
              - name: prometheus-data-volume          persistentVolumeClaim:
                  claimName: prometheus-data-pvc        - name: prometheus-conf-volume          configMap:
                  name: prometheus-conf        - name: prometheus-rules-volume          configMap:
                  name: prometheus-rules      tolerations:
              - key: node-role.kubernetes.io/master          effect: NoSchedule---kind: ServiceapiVersion: v1metadata:
        annotations:
          prometheus.io/scrape: 'true'
        labels:
          app: prometheus  name: prometheus-service  namespace: ns-monitorspec:
        ports:
          - port: 9090
            targetPort: 9090
        selector:
          app: prometheus  type: NodePort
    • 创建资源

      [root@master-kubeadm-k8s prometheus]# kubectl apply -f prometheus.yamlclusterrole.rbac.authorization.k8s.io/prometheus created
      serviceaccount/prometheus created
      clusterrolebinding.rbac.authorization.k8s.io/prometheus created
      configmap/prometheus-conf created
      configmap/prometheus-rules created
      persistentvolume/prometheus-data-pv created
      persistentvolumeclaim/prometheus-data-pvc created
      deployment.apps/prometheus created
      service/prometheus-service created
    • 查看资源

      [root@master-kubeadm-k8s prometheus]# kubectl get pods -n ns-monitorNAME                          READY   STATUS              RESTARTS   AGEnode-exporter-dsjbq           1/1     Running             1          26m
      node-exporter-mdnrj           1/1     Running             1          26m
      node-exporter-sxwxx           1/1     Running             2          26m
      prometheus-5f7cb6d955-mm8d2   1/1     Running             1          28s[root@master-kubeadm-k8s prometheus]# kubectl get pv -n ns-monitorNAME                 CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                            STORAGECLASS   REASON   AGEprometheus-data-pv   5Gi        RWO            Recycle          Bound    ns-monitor/prometheus-data-pvc                           53s[root@master-kubeadm-k8s prometheus]# kubectl get pvc -n ns-monitorNAME                  STATUS   VOLUME               CAPACITY   ACCESS MODES   STORAGECLASS   AGEprometheus-data-pvc   Bound    prometheus-data-pv   5Gi        RWO                           60s[root@master-kubeadm-k8s prometheus]# kubectl get svc -n ns-monitorNAME                    TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGEnode-exporter-service   NodePort   10.109.226.6     <none>        9100:31672/TCP   27m
      prometheus-service      NodePort   10.101.128.125   <none>        9090:31615/TCP   64s
    • 测试

      image.png

2.2.4 部署Grafana监控UI

  • 准备YAML文件

    • grafana.yaml

      apiVersion: v1kind: PersistentVolumemetadata:
        name: "grafana-data-pv"
        labels:
          name: grafana-data-pv    release: stablespec:
        capacity:
          storage: 5Gi  accessModes:
          - ReadWriteOnce  persistentVolumeReclaimPolicy: Recycle  nfs:
          path: /nfs/data/grafana    server: 192.168.50.111---apiVersion: v1kind: PersistentVolumeClaimmetadata:
        name: grafana-data-pvc  namespace: ns-monitorspec:
        accessModes:
          - ReadWriteOnce  resources:
          requests:
            storage: 5Gi  selector:
          matchLabels:
            name: grafana-data-pv      release: stable---kind: DeploymentapiVersion: apps/v1metadata:
        labels:
          app: grafana  name: grafana  namespace: ns-monitorspec:
        replicas: 1
        revisionHistoryLimit: 10
        selector:
          matchLabels:
            app: grafana  template:
          metadata:
            labels:
              app: grafana    spec:
            securityContext:
              runAsUser: 0
            containers:
              - name: grafana          image: grafana/grafana:latest          imagePullPolicy: IfNotPresent          env:
                  - name: GF_AUTH_BASIC_ENABLED              value: "true"
                  - name: GF_AUTH_ANONYMOUS_ENABLED              value: "false"
                readinessProbe:
                  httpGet:
                    path: /login              port: 3000
                volumeMounts:
                  - mountPath: /var/lib/grafana              name: grafana-data-volume          ports:
                  - containerPort: 3000
                    protocol: TCP      volumes:
              - name: grafana-data-volume          persistentVolumeClaim:
                  claimName: grafana-data-pvc---kind: ServiceapiVersion: v1metadata:
        labels:
          app: grafana  name: grafana-service  namespace: ns-monitorspec:
        ports:
          - port: 3000
            targetPort: 3000
        selector:
          app: grafana  type: NodePort
    • grafana-ingress.yaml

      #ingressapiVersion: extensions/v1beta1kind: Ingressmetadata:
        name: grafana-ingress  namespace: ns-monitorspec:
        rules:
        - host: monitor.k8s.sunny.com    http:
            paths:
            - path: /        backend:
                serviceName: grafana-service          servicePort: 3000
  • 创建资源

    [root@master-kubeadm-k8s prometheus]# kubectl apply -f grafana.yamlpersistentvolume/grafana-data-pv created
    persistentvolumeclaim/grafana-data-pvc created
    deployment.apps/grafana created
    service/grafana-service created[root@master-kubeadm-k8s prometheus]# kubectl apply -f grafana-ingress.yamlingress.extensions/grafana-ingress created
  • 查看资源

    [root@master-kubeadm-k8s prometheus]# kubectl get deploy -n ns-monitorNAME         READY   UP-TO-DATE   AVAILABLE   AGEgrafana      1/1     1            1           2m52s
    prometheus   1/1     1            1           6m41s[root@master-kubeadm-k8s prometheus]# kubectl get pv -n ns-monitorNAME                 CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                            STORAGECLASS   REASON   AGEgrafana-data-pv      5Gi        RWO            Recycle          Bound    ns-monitor/grafana-data-pvc                              3m10s
    prometheus-data-pv   5Gi        RWO            Recycle          Bound    ns-monitor/prometheus-data-pvc                           7m[root@master-kubeadm-k8s prometheus]# kubectl get pvc -n ns-monitorNAME                  STATUS   VOLUME               CAPACITY   ACCESS MODES   STORAGECLASS   AGEgrafana-data-pvc      Bound    grafana-data-pv      5Gi        RWO                           3m14s
    prometheus-data-pvc   Bound    prometheus-data-pv   5Gi        RWO                           7m4s[root@master-kubeadm-k8s prometheus]# kubectl get svc -n ns-monitorNAME                    TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGEgrafana-service         NodePort   10.111.192.206   <none>        3000:31828/TCP   3m5s
    node-exporter-service   NodePort   10.109.226.6     <none>        9100:31672/TCP   33m
    prometheus-service      NodePort   10.101.128.125   <none>        9090:31615/TCP   7m4s[root@master-kubeadm-k8s prometheus]# kubectl get ingress -n ns-monitorNAME              CLASS    HOSTS                   ADDRESS   PORTS   AGEgrafana-ingress   <none>   monitor.k8s.sunny.com             80      2m15s
  • 测试

    账号密码都是 admin

    image.png



作者:Suny____
链接:https://www.jianshu.com/p/6d3c29f87bcc
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。