This commit is contained in:
baim0
2024-03-09 13:38:35 +08:00
parent d20f09f953
commit 17977970c4
229 changed files with 10417 additions and 0 deletions
BIN
View File
Binary file not shown.
@@ -0,0 +1,279 @@
# 【学习前准备工作 强调几点注意事项】
之前看过序章的朋友 一定还记得大米跟大家提过, 一个最最简单的网站(静态网站)基础框架组成部分是什么?
那就是 NGINX(WEB服务器) + 服务器 + 操作系统(Linux)
# 这里给大家补充说明几点
第一: 首先 咱们先来说一下Linux这个重头戏的组成部分,Linux因为它的开源以及稳定等等的优质特性,现如今已经基本统一了服务器操作系统的标准,说真的 现在去机房 再想找出一台不是跑着Linux的服务器(跑着其他的系统) 估计挺难的了
在我们的专栏中(包括后续的专栏)全部都是基于Linux给大家搭建环境 并讲解的
所以 本专栏需要大家对Linux 有一定的基础后 再继续阅读
(本专栏中 涉及到的 过于基础的Linux知识 我们就不再额外花篇幅讲解了 )
第二:关于服务器,在序章的时候 跟大家说过 它指的是一台物理服务器 , 所谓的互联网 其实不过就是 由无数无数的物理服务器连接在一起 最终绘制而成的(比较有经验的同学 可能会抬杠了, 谁说都是物理服务器的,现在很多不都是云虚拟机了吗? 问的对,现在确实很多中小企业 都不再购买真正的物理服务器来搭建自己的"互联网产品", 但是 归根到底,云机只不过是针对企业用户或者个人用户来说的, 从技术层面上说, 云机是通过虚拟化技术 在物理服务器上 划分出的区域 本质永远脱离不了 本体的物理服务器)
然而 在我们的专栏学习的过程中,服务器是每个人必须准备的, 然而为了学习而购买一台上万的物理服务器肯定不现实 也没有必要
(因为我们并不是真的要 搭建一个被好几万人访问的网站 )
# 这里推荐大家 两种方式来 搭建我们的服务器(虚拟机)
第一种 (推荐):在活动折扣期间 购买任意云平台的 最低配置虚拟机两台(学习过程中 最少使用两台 三台最佳 后面会讲解为什么)
这样的做法 是大米比较推荐的,虽然会产生一定的费用(折扣活动期间 最低配的一台云机 一年也不过100-200块钱而已 比较划算 )然而 灵活性非常的高 因为购买的云机 可拥有公网IP地址 可以在任意地点登录使用和维护 这样以来 就对地点不再局限 可以在任意地方进行远程登录
另外 现在大多数云平台的云机 都支持按量付费 也就是说 用的时候才会产生费用 (一天几毛钱到几块钱 看配置高低 和 使用时长了 基本按照小时出账单) 用完后释放 就不会再产生费用, 需要注意的就是 千万别弄高配置 高带宽 那费用成指数上升 就很吓人了 一切就按最低配置来即可 学习足够用了
另外还有一个好处,就是多接触各类云计算平台的使用方法 对以后想从事运维工程师相关工作来说 是一个非常好的锻炼和准备
第二种(免费):这个自然就是 使用我们的笔记本 或者 PC台式机 安装Linux虚拟机
估计大多数人还是会选择这样的方式来安装Linux虚拟机 , 毕竟免费嘛 ^\_^
这里 强烈推荐大家使用 virtual box 来安装虚拟机(因为简单 又免费) , Linux镜像选择Centos6/7 即可
关于如何在windows上使用virtual box安装虚拟机,这个非常简单 度娘一搜就一堆 这里就不再跟大家啰嗦了
(唯一有点麻烦的就是 需要提前下载好一个 Centos.ios的镜像文件 这个文件很大 最好常备一份儿)
最稳妥的方式 推荐大家在接有网线的 台式机 或者 笔记本上 安装虚拟机三台, 网络选择"物理桥接即可"
然后每台Linux虚拟机 手动分配固定IP (不要用DHCP自动分配 不然时不时变IP非常讨厌, 所以服务器99%的情况 都是手动分配固定IP)
第三:解释一下什么叫做 静态站点和动态站点
关于什么是静态和动态站点 我们之前跟大家提过两次, 在这里 咱们来解释一下意思
静态网站? 网页都是静止不动的? 只要画面有变动就成动态网站了? 是这个意思吗?
其实不是的哦 -\_-
所谓的静态网站 准确的定义 是网站所提供的网页内容对于所有来访的用户都是保持一致 不随用户的互动动作而计算且发生变化 除非是服务器端的静态资源(例如 文字 图片 视频 动画 声音)发生了改变
这里理解起来可能会产生误区,比如 有的学员可能就会问 比如打开一个纯看图片的网站,这算是静态了对吧, 可是当我鼠标点击站内另外一个图片链接的时候,画面就变了唉 这不就是随着我的动作发生改变了吗? 变成动态网站了?
大米给大家的解释是,没错 这里点击另外的图片 确实是画面发生了改变, 但是这并不是动态网站效果 为什么呢?
因为 另外的图片链接原本就存在在站内,只不过是当你点击到了 才看到而已, 这里的动作只是发生了页面跳转 而并不是根据你的动作和输入 经过计算后发生改变
这么解释 估计还可能有同学不是很明白,不用太着急 接下来 咱们看一下动态网站的定义 再返回头来看 就明白多了
# 那什么是动态网站呢?
最直接简单的例子 就是一个登录欢迎页面
![](assets/1593412128-ade068bba79ca928a207744500bb2caf.png)
就像上面这张图这样, 大家都见过吧, 网页一上来就先让你输入用户名和密码
输入完成之后(比如:张三 123456) 然后会有一个对应的"程序" 把你输入的账号和密码 携带着去到数据库里查验
看看这个账号是不是存在, 再看看密码是否正确, 如果错误 就返回失败, 如果正确 就返回 一个页面写着 欢迎你 张三
这种就属于动态网站的功能, 因为网站跟用户有交互的动作, 用户会有数据的提交 ,而且根据每个用户输入数据的不同 返回的结果可能都不一样 比如 李四登录成功 就显示 欢迎你 李四, 别人登录成功的话 就会变了
除此之外 动态网站还有两个非常重要的组件 , 那就是 网站后面有程序代码在运行和判断 还有动态网站 必然存在数据库的支持 (静态网站一般没有)
另外 从URL(也就是你输入的网址 也可以进行判断)
静态网站 更多以 .htm .html .xml结尾, 而动态网站 更多以 .php .asp .jsp .cgi (CGI通用网关接口 是最早实现动态网站的技术 大米之前在序章提过的)等等结尾 在动态网页网址中有一个标志性的符号——“?”,如有这样一个动态网页的地址为:
[http://www.testdami.com/index.php?id=12345](http://www.testdami.com/index.php?id=12345)
其实现阶段来看 动态网站和静态网站 基本上已经不是独立存在的了, 绝大多数情况 都是混合再一起的网站 既有静态又有动态
就拿我们平时尝尝访问的几个门户网站来说, 一般都带有新闻首页对吧 上面附带着大量的静态页面内容(文字 图片 等等), 然而与此同时 所有门户网站 都支持 用户注册登录 邮箱 搜索等等动态网站的内容
在我们的专栏的课程中 我们先做出和搭建一个最简单的静态网站为目标 后面会再加入动态网站的内容
提问:既然网站有静态和动态之分 那NGINX是不是都可以支持呢?
回答:NGINX本身只有处理静态WEB的能力 ,处理动态站点的话 NGINX本身不会自己去处理 但是它拥有的模块 (fastcgi->主要用来传递PHP , uwsgi->主要用来传递python web)会识别动态请求 进而把请求传递给后端的其他程式来处理
# 【开始准备我们学习用的Linux服务器吧】
接下来 咱们就可以着手准备我们的服务器了
大米假设大家到了这个时候 所有的Linux虚拟机都已经安装完毕了哦 ^^
接下来 我们的服务器 需要完成一些环境的配置工作 才可以正常使用的哦
* 首先是Linux的FQDN设置
首先咱们先确认 我们的虚拟主机拥有 完整主机名(FQDN) 并且可以相互ping通哦
Linux上完整主机名的配置 不光是对于NGINX, 对于其他任何服务来说 都是非常重要的
有些服务 如果检测不到正确主机名的配置 就会不断报错 甚至无法使用
另外 在生产环境中的Linux服务器,完整主机名的配置是硬性要求 因为当服务器数量大了以后,混乱的主机名会给维护带来极大的不便
完善的主机名 最简单的理解 就是 一个主机名+域名 来组成 而并不仅仅只是给机器起个名字这么简单哦 ^^
主机名:代表的是机器本身 域名:代表的是处在哪个域
例如 server001.study.com. 主机名是 server001 , 域是 study.com
就用Centos6为例子 我们按照如下的方式操作和最终验证即可
![](assets/1593412128-295e387ee8be58083494284ffe6cae87.png)
如上图中所示 一共有 三个地方需要修改 一个是 /etc/sysconfig/network 当中定义 HOSTNAME
再一个就是 /etc/hosts下面的设置
/etc/hosts 是一个非常重要的 Linux解析配置文件, Linux最早就是依靠它 来完成 机器名和ip地址的转换工作
比如 在上面图中 /etc/hosts文件 定义了咱们三台机器的 机器名和IP地址对应关系
server01.example.com server02.example.com server03.example.com
另外 在每一行的最后还定义了一个短名称 这个叫做别名
这样以来, 三台机器之间 就可以相互使用 完整机器名 或者其别名 来调用了
![](assets/1593412128-b67f3e186f84f2632aad07b3743c767f.png)
最后的 hostname -f (-f代表FQDN) 可以正确显示完整主机名 即可
接下来 我们检查Linux内核版本 请保证内核版本在2.6以上
为什么呢? 因为2.6以上版本的Linux内核 才支持epoll
说到这里会有疑问了 epoll又是个啥?(经常配置nginx的同学 一定见过 或者听说过这个词)
其实这里相关的有三个主要词汇 select , poll , epoll
这几个是nginx配置文件中 可选的配置项(events模块 ,后面会讲到)
这里容易有个误区 ,以为这三个选项 是nginx独有的"特性" ,实则不然
select/poll , epoll 其实是Linux内核中 多路复用I/O的 三种方法,跟nginx其实并没有直接的关系
如果要真的完全解释清楚 这三种模式在Linux内核中的实现方法 是相当复杂的
我们在当前可以先简单按照如下 理解即可
首先 这三种模式 都是Linux内核 为了解决处理高并发问题而设计的
随着内核的发展 诞生的先后顺序为 select -> poll -> epoll , epoll可以理解为 poll方法的增强版
其实 这三种方法的本质 都是多路复用 提高内核处理网络连接的工作效率
那多路复用又是个啥?
以一个生活中的例子来解释一下吧(如下一段 取自别处 个人认为很恰当的例子).
假设你在大学中读书,要等待一个朋友来访,而这个朋友只知道你在A号楼,但是不知道你具体住在哪里,于是你们约好了在A号楼门口见面.
如果你使用的阻塞IO模型来处理这个问题,那么你就只能一直守候在A号楼门口等待朋友的到来,在这段时间里你不能做别的事情,不难知道,这种方式的效率是低下的.
现在时代变化了,开始使用多路复用IO模型来处理这个问题.你告诉你的朋友来了A号楼找楼管大妈,让她告诉你该怎么走.这里的楼管大妈扮演的就是多路复用IO的角色.
既然 明白了什么是 多路复用 那么我们看下三种方法的区别
select版大妈做的是如下的事情:比如同学甲的朋友来了,select版大妈比较笨,她带着朋友挨个房间进行查询谁是同学甲
而poll和select差不太多, poll只是解决了 select的 FD问题, 但是本质并无大变化 依然无法很好支持高并发(文件描述符的限制 ,感兴趣的同学 可以自己搜索相关文章)
epoll版大妈就比较先进了,她记下了同学甲的信息,比如说他的房间号,那么等同学甲的朋友到来时,只需要告诉该朋友同学甲在哪个房间即可,不用自己亲自带着人满大楼的找人了
理解到这里 目前就够用了
接下来 检查内核版本的方法很简单 如下
符合我们的要求
![](assets/1593412128-4217fe32b4f59017e2dc99f68e9102f1.png)
接下来 咱们在 Linux上 (Centos) 使用yum 安装一些软件, 这些软件都是nginx必备的 我们分别来解释一下
(yum就不用多介绍了吧 Centos/Redhat Linux 最重要的 软件包安装管理工具)
如下安装的这些插件 如果是平时经常配置NGINX的同学一定很常见,但是我们要知其所以然 不能总是跟着网上搜来的安装教程复制几个命令一跑完事
# 1: gcc编译器的安装
C/C++语言编译器 我们后面的课程中 nginx最终都会采用源代码编译的方式来安装
(Linux下两种安装nginx的方式, yum 和 源代码编译 , yum安装的 直接就可以执行,简单方便 但是无法自由扩展nginx功能, 源代码编译的方式 是把nginx的源代码下载下来,然后我们自己通过编译器 自己按照需求 生成我们需要的nginx ,这个方式麻烦的多 但是 却自由度很高 也是企业中绝大部分的 部署开源软件的方式)
况且 后面也会讲到 自行修改开发nginx模块, 这些都离不开C/C++编译器的哦
(不管是想学好Linux 还是深入Linux上运行的各种开源软件, C语言都是必不可少的)
安装的方式很简单
```plain
yum install -y gcc gcc-c++
```
# 2: PCRE库的安装
PCRE这个平时应该也挺常见的吧 :) , 其实它的全称是 Perl Compatibel Regular Expressions : Perl兼容正则表达式
意思也就是说 ,如果你的NGINX以后在配置的过程中 需要使用正则表达式 就必须装这个插件(PCRE)
(所谓的正则表达式 就是一种使用各类特殊符号 来达到匹配或者过滤 字符串的目的 是一种通用技能)
类似就是这样的配置 :
```plain
rewrite ^(.*)index\.php\?$ http://10.86.255.119$request_uri;
```
看着就头晕有点 对不? ^\_^ (其实这已经算很简单的 正则了)
确实 正则表达式 不好学 很不容易找规律 而且最关键的是不好记忆 但是又非学不可
(后面我们会有专门的篇幅来学习它)
安装的方式也很简单
```plain
yum install -y pcre pcre-devel
```
3: zlib库的安装
nginx处理HTTP响应的时候,是可以支持gzip先压缩 后传输的方式 进而来减少网络传输量的哦
如果你希望nginx开启这个功能,就得把zlib编译进入nginx中
```plain
yum install -y zlib zlib-devel
```
4: OpenSSL库的安装
HTTPS大家不陌生吧 ,HTTP+SSL形成的更安全的传输协议 , 这个库几乎是默认必须安装的
现在基本上大部分网站 都支持HTTPS 只有一部分接口依然走HTTP , 这也是大势所趋了
```plain
yum install -y openssl openssl-devel
```
上面安装的几个软件包 只不过是完成Web服务器最基本功能所需的,后续 随着我们不断的深入学习,还有可能继续再增加别的软件包扩展 目前先了解清楚 这四个
必要的软件包装好了以后,咱们最后来看一下Linux内核参数的优化
大米先给大家解释一下 为什么要优化内核
我们平时总说 性能调优 性能优化之类的话对吧 其实说白了 就是通过各种参数调整 让服务器的性能达到最大化
一个服务器上其实是存在这么几层的嵌套关系的 从最底层开始 依次是
物理硬件 操作系统 服务软件 程序代码
其实优化的最终目的 是发挥物理硬件的最大效能 因为物理资源是不能随意改变的
至于硬件往上的部分 其实是层层嵌套的关系 一个会制约另一个 就好比 操作系统中的最大连接数没有放开 但是服务软件配置中的放开了 也是没有作用的 所以 这种嵌套关系有点像接在一起的 水龙带 光是一段很宽没用 必须都宽才行
接下来 咱们看下 针对咱们即将安装的Nginx , Linux的内核参数优化 应该掌握些什么
由于默认的Linux内核参数是最通用的场景,不符合咱们一直谈的支持高并发的特性,所以 我们需要做出一些初步的调整 把操作系统 这一层的"水龙头" 放开一些 ^\_^
```plain
fs.file-max = 100000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_max_tw_buckets = 5000
net.ipv4.ip_local_port_range = 1024 61000
```
内核的参数项目很多,因为我们在这儿不是专门讲Linux内核优化的 所以先拿出几个例子来简单说一下
file-max 进程可以同时打开的最大句柄数 (听说过Linux一切皆文件的说法吧) 这一项直接限制最大并发连接数
tcp\_tw\_reuse 允许将处于 TIME\_WAIT状态的socket重新用于新TCP连接, 平时我们在服务器上 应该能看到大量的 TIME\_WAIT状态的链接对不?(netstat -an | grep -i wait) ,这对于处理高并发的服务器来说 意义很大
keepalive\_time 这个参数表示 TCP发送keepalive消息的频度 ,默认是2个小时 这太长了。。。 设置的小一些 有利于清理无效的链接
port\_range TCP/UDP 连接的端口范围 , 建立一个连接就肯定会消耗掉一个端口 ,如果不把这个值改大了 高并发的时候 连接就无法建立了。。
做好如上的准备后 我从下一篇开始接触NGINX啦~
@@ -0,0 +1,160 @@
## 10.Nginx和PHP永不分家——正确理解和部署NG+PHP
# \[复习一下 什么是动态网站 什么是静态网站\]
我们早在序章的时候,就给大家讲过 什么是静态网站 什么是动态网站 还记得吧?^\_^
那么从本节课开始,我们逐步来接触动态网站的各种相关知识
之前已经讲过的东西 咱们不再啰嗦了, 仅仅以两个简单小例子来总结一下
# 什么是静态网站?
打开网页后 看到很多图片, 看到大量的文字,听到了一首歌 看了一段电影 这种都属于静态网站的内容
# 什么是动态网站?
张三登录网页版邮箱,和李四登录同一个网页版邮箱,两人都必须输入各自的账号和密码, 如果输入错误 则网页返回 禁止登录
这种就属于动态网站内容
所以说 静态网站返回的东东 都是事先准备好的 存在服务器上的东西 ,谁来看都一样的结果
动态网站返回的东东 就是跟每个不同用户相关了。看用户怎么输入 然后由程序进行各种判断后 再返回结果 结果不是一成不变的
# \[nginx怎么处理动态呢\]
nginx是啥? webserver对吧,http服务器
nginx自身能处理动态请求吗? 回答: 不能
那遇到动态请求可咋办呢? 回答: 让"别人"来帮处理就好了嘛 ^^
咱们之前 是不是学过 nginx的反向代理?
反向代理 说白了 就是把特定的请求 扔给后端的服务器 或者"服务" 来处理嘛
所以说 当webserver遇到动态请求 而自身不能处理的时候 它就会把请求扔给能处理 动态请求的 程序进行处理
能处理php程序的软件 我们又称为php解释器
交给php解释器处理很好,但是,php解释器如何与webserver进行通信呢?
为了解决不同的语言解释器(如php、python解释器)与webserver的通信,于是出现了cgi协议。只要你按照cgi协议去编写程序,就能实现语言解释器与webwerver的通信。如php-cgi程序。
(CGI 通用网关接口 是实现动态网站的重要里程碑 当前所有的动态网站 或者 动态HTTP 都是基于CGI为基础 改良而来)
# fast-cgi的改进
有了cgi协议,解决了php解释器与webserver通信的问题,webserver终于可以处理动态语言了。
但是,webserver每收到一个请求,都会去fork一个cgi进程,请求结束再kill掉这个进程。这样有10000个请求,就需要fork、kill php-cgi进程10000次。
# 有没有发现很浪费资源?
于是,出现了cgi的改良版本,fast-cgi。fast-cgi每次处理完请求后,不会kill掉这个进程,而是保留这个进程,使这个进程可以一次处理多个请求。这样每次就不用重新fork一个进程了,大大提高了效率。
# php-fpm是什么
php-fpm即php-Fastcgi Process Manager.
php-fpm是 FastCGI 的实现,并提供了进程管理的功能。
进程包含 master 进程和 worker 进程两种进程。
master 进程只有一个,负责监听端口,接收来自 Web Server 的请求,而 worker 进程则一般有多个(具体数量根据实际需要配置),每个进程内部都嵌入了一个 PHP 解释器,是 PHP 代码真正执行的地方。
# \[nginx和PHP的安装\]
接下来 咱们就动手开始安装PHP吧 ^\_^
请先自行下载 最新的php 源代码安装的版本 (当前最新的版本是php7.x)
[https://secure.php.net/get/php-7.3.1.tar.gz/from/a/mirror](https://secure.php.net/get/php-7.3.1.tar.gz/from/a/mirror)
安装依赖库
> yum install libxml2\*
下载之后解压并进入在解压文件中
```coffeescript
./configure -enable-fpm
make && make install
到/usr/local/etc/目录下,将php-fpm.conf.default拷贝一份成php-fpm.conf
[root@server01 php-7.3.1]# cd /usr/local/etc/
cp php-fpm.conf.default php-fpm.conf
```
然后在编辑php-fpm.conf配置文件
;最后一行改成如下
```makefile
include=/usr/local/etc/php-fpm.d/*.conf
进入到/usr/local/etc/php-fpm.d/目录下,将www.conf.default拷贝一份成www.conf
/usr/local/etc/php-fpm.d# cp www.conf.default www.conf
修改 www.conf的用户
编辑www.conf文件,将user和group改成和nginx.conf中的user和group一致
user=www
group=www
```
没有这个用户的话 就手动添加一个即可
```plain
useradd www
```
然后再次 启动 php-fpm
```plain
/usr/local/sbin/php-fpm
```
查看是否启动成功
```plain
ps -ef | grep php-fpm
root 3691 1 0 18:49 ? 00:00:00 php-fpm: master process (/usr/local/etc/php-fpm.conf)
www-data 3692 3691 0 18:49 ? 00:00:00 php-fpm: pool www
www-data 3693 3691 0 18:49 ? 00:00:00 php-fpm: pool www
root 4982 29553 0 18:59 pts/1 00:00:00 grep --color=auto php-fpm
netstat -tnl | grep 9000
tcp 0 0 127.0.0.1:9000 0.0.0.0:* LISTEN
修改nginx的配置文件,支持php文件的解析,找到location的添加位置,在后面添加下面这个location
vim /usr/local/nginx/conf/nginx.conf
location ~ \.php$ {
root /var/www/php;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
```
从这段配置文件中,我们可以读出
如果location判定 URL以.php后缀结尾,则指定到 /var/www/php/下面去找资源
动态的请求 一律通过 fastcgi 提交给 本地的9000端口来执行(php-fpm的默认工作端口)
指定 fastcgi的首页为 index.php
添加一个 用于测试php的 index.php
```xml
vim /var/www/php/index.php
<?php phpinfo(); ?>
```
重启nginx后 再次访问 ip/index.php 如果可以看到如下的php版本信息 则表示php安装一切正常
![](assets/1593412440-3791f36d3112301e5dd2fdf74cdd3f4b.png)
到这里 ,我们的php跟nginx就正确的结合在一起了 :)
后面 我们还会学习 如何自行开发一个简单的php程序哦
@@ -0,0 +1,340 @@
## 11.认识动态网站并学会自己制作第一个动态首页(上篇)
\[一起认识动态站点的页面\]
所谓的动态站点, 就是有一套程序在服务器上运行, 然而 这个程序是在等什么东西到来呢?
其实就是在等待用户的输入啦
我们之前也跟大家提过几次了,动态站点就是跟用户的互动 , 用户发起自己的提交 之后呢 交由程序来做后面的处理 结果根据输入的具体情况而定 这就是所谓的动态网站处理的模式
那么大家想一想 网站要跟用户互动, 需要什么呢?
是不是 至少要让用户 先能看到一个界面才可以啊
而作为一个网站 它的界面是什么呢?
那就是 由HTML所形成的 页面啦
举个最最简单的例子吧
还记不记得 咱们之前的章节中 在学习nginx作静态首页的时候 用过如下这么一段HTML?
```xml
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx1!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h2>Welcome to nginx! This is dami class ^_^ </h2>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
```
其实 这就是nginx自带的一个欢迎首页HTML 告诉你nginx现在工作正常 另外 页面上 也附带一些链接的信息
这个就是一个 非常简单的HTML了
HTML语言的格式 大家也可以从这里 看出个端倪了
它是一种 标签语言
什么是标签呢?
看上面 最开始 是不是 <html>开头?
然后最后 还有一个 </html>结尾
这就是标签的形式 <html> 中间写具体的内容</html>
<h2> </h2> <p> </p> 这种 属于字体的标签 也就是说 在这个标签中输入的内容 会以不同的形式 展现我们需要的文字
<body></body> 这个标签呢? 这个叫做实体标签 中间的内容 会作为网页传输的"实体" .
(这里有点抽象,我们可以理解为 一个动态网站 如果要让用户互动 其实指的是 用户在页面提交的内容的传递 传递什么东西呢?那就是 实体当中的所有的内容, 这个内容最终会 交给谁? 当然就是交给 网站后面的动态程序咯 )
关于HTML语言 本身是一门很大的编程学问 我们在这里 只是让大家快速入门而已
如果真的想把HTML学精通 大家可以私下去看 HTML教学官网 进行更系统的学习
最终结果图 是这样展现的
![](assets/1593412471-45b63cae4474e2ab0f3dfbd7dd10014a.png)
不过 大家看出一个问题没有 这个展现出来的 就是地地道道的静态HTML页面了
所有用户看到的都一样,而且 关键是 没有提供 可以让用户输入互动内容的"地方"
想要实现动态网站, 就必须首先在HTML上 就得提供 这个 能让用户自己输入的 "地方"
"地方" 是怎么个概念呢? 别着急 接下来一个单元 就会实现啦 ^\_^
\[动态站点的HTML 初识表单\]
## 别的先不说 咱们把 如下这一段HTML 放到你的主页目录中
```xml
<!DOCTYPE HTML>
<html>
<meta charset="utf-8">
<body>
<form action="login.php" method="post">
服务器类型: <input type="text" list="servertype_list" name="servertype" /> <br>
<datalist id="servertype_list">
<option label="cloudserver" value="云主机" />
<option label="ph_server" value="物理机" />
</datalist>
服务器主机名(不包含域名): <input type="text" name="servername" /> <br>
服务器域名: <input type="text" list="serverdomain_list" name="serverdomain"> <br>
<datalist id="serverdomain_list">
<option label="example.com" value="example.com" />
<option label="test.admin.com" value="test.admin.com" />
</datalist>
服务器CPU数量: <input type="text" list="cpu_cores" name="servercpu"> <br>
<datalist id="cpu_cores">
<option label="cpu_cores_2" value="2" />
<option label="cpu_cores_8" value="8" />
<option label="cpu_cores_16" value="16" />
<option label="cpu_cores_32" value="32" />
</datalist>
服务器内存: <input type="text" list="server_ram" name="server_ram"> <br>
<datalist id="server_ram">
<option label="server_ram_4" value="4" />
<option label="server_ram_8" value="8" />
<option label="server_ram_16" value="16" />
<option label="server_ram_32" value="32" />
<option label="server_ram_64" value="64" />
</datalist>
服务器硬盘: <input type="text" list="server_disk" name="server_disk"> <br>
<datalist id="server_disk">
<option label="server_disk_1" value="200-300" />
<option label="server_disk_2" value="300-500" />
<option label="server_disk_3" value="500-1T" />
<option label="server_disk_4" value="1T-5T" />
</datalist>
<input type="submit" value="提交"/>
</form>
```
* * *
上面这一大段 就是我给大家准备的 动态站点的HTML页面了
先来看一下效果吧
![](assets/1593412471-757fcc276074c79a426150d967938740.png)
看到了没有啊? ^\_^ 这个样子 就是我们期待的效果
本身这个页面 是之前大米在公司中 开发一款叫做CMDB的软件时候 所制作的一小部分 页面
关于什么是CMDB 我们下篇中再跟大家 细细介绍一下 这里 我们就只要先知道 是一个作为 服务器信息录入数据库的工具即可
这就是一个典型的动态网站了
从上面的截图 我们可以看到 这个页面本身有文字的提示 让你输入 服务器的类型 和 名称 等等
右边有输入框,选择框 等等
(用户的输入的形式 有很多种 即可以直接在一个框中输入你的内容, 也可以通过一个事先定义好的 选择框 选择点击而作为输入)
这种可以提供用户输入的HTML页面 我们称作 HTML表单
我们注意开头 这个标签
```javascript
<form action="login.php" method="post">
</form>
```
这个form标签内部的所有内容 就是表单的内容了
说的这么热闹 那表单内容都是什么啊?
其实 这些输入框 选择框 以及旁边出现的文字 就都是表单的内容了
![](assets/1593412471-0234c6a73256a50b162bd54cc184f942.png)
为什么要有 这个表单呢?
从字面理解, 表单表单 => 是不是很像 表格的意思?
我们日常生活中 填写一份"表格"是为了 干啥呢?
当然是为了 提交出去啊 ^^
(就好比 去面试的时候 你是不是 得先填写个人信息的表格 然后交给 前台的妹妹 然后才能开始面试呢?^\_^)
所以说 表单的作用 简单的说 就是把用户输入的"内容"(不管是什么形式的 直接输入框 或者选择框 或者其他形式)
放进一个"表格中" ,然后统一提交给后面的 程序处理
\[表单制作 细讲\]
接下来 咱们细讲一下表单的制作过程
我们先拿出 页面中的 第一个小部分 来细说一下表单的功能
![](assets/1593412471-43a45e99dd61011032aab12c1dedff6a.png)
看到这里没有, 选择服务器的类型 点击之后 右边是一个选择框 并且给了咱们两个固定选项
这个玩意 是怎么实现的呢?
请看如下这一段HTML代码 (学会这一个 其他的就不是问题了)
```xml
服务器类型: <input type="text" list="servertype_list" name="servertype" /> <br>
<datalist id="servertype_list">
<option label="cloudserver" value="云主机" />
<option label="ph_server" value="物理机" />
</datalist>
```
这段代码中 服务器类型: 这里是显示的文字 这个很好理解
接下来 右边是一个 <input....>开始的标签
<input /> 就是最重要的 网页输入框的 标签 , 它提供这种白色的输入框 让你输入内容
而咱们这个例子中的输入框其实是有点特殊的
即可以作为 直接输入框
(意思就是说 可以在框中 任意输入你想提交的东西)
也可以作为 选择输入框
(意思就是说 也可以 通过选择 来进行输入)
那么 一个<input>在定义出来之后 , 其实默认就是 直接输入框了
![](assets/1593412471-9de0cb5635184c63d61ae83623561daa.png)
看到没有 可以随便输入
然而 它又支持 选择方式输入
![](assets/1593412471-ed97395769a14bb535e9a307d1d81024.png)
这个是 由下面的部分来定义的
```xml
服务器类型: <input type="text" list="servertype_list" name="servertype" /> <br>
<datalist id="servertype_list">
<option label="cloudserver" value="云主机" />
<option label="ph_server" value="物理机" />
</datalist>
```
看 <datalist> </datalist> 这个
这个就是作为 一个选择框的 定义了
接下来两个 <option>标签 什么意思呢? 就是 这个选择框 必须要给用户提供 事先定义好的选项啊
所以这里 我们可以看到 这个选择框的 两个事先定义好的选项 "云主机" "物理机"
明白了 这里之后 后面其他的 选择框 都是大同小异
\[表单的矢量化 提交概念的引入\]
什么叫做矢量化呢? 所谓的矢量化 就是把某种 原本用做"看的东西" 变成能用 能识别的意思
在咱们这里 其实指的就是 用户在页面提交的"内容" 可以让后面的程序 识别到 并且接受到
怎么来做呢?
(接下来的内容 难度较高 请务必仔细阅读 会有巨大的收获)
我们还是看这一段 HTML
```xml
服务器类型: <input type="text" list="servertype_list" name="servertype" /> <br>
<datalist id="servertype_list">
<option label="cloudserver" value="云主机" />
<option label="ph_server" value="物理机" />
</datalist>
```
```plain
注意在 <input ....中 有一个关键词 叫做 list="servertype_list"
什么意思呢? 就是说 <input> 这一行 只是实现了 一个 输入框
而输入框中 选择内容的时候 , 其实是 从下面的 <datalist>中定义好的选项 来获取的
接下来 看 <input 最右边的 name="servertype" 这里就到了 一个非常重要的关键了
name的意思是 把这个<input> 输入框 起了一个"名字"
```
但是这个 名字 可不是随便给它起个代号哦
这个名字 其实是作为一个 唯一的识别ID 这个ID作为一个唯一的标识 提交给后面 我们即将讲到的 PHP程序
PHP程序 就是依靠这个 ID 来识别输入框 并且准确的把 输入框中的内容 提取出来的哦
总体来说 大家可以通过如下这张图 来理解 我刚才说的一段话
![](assets/1593412471-089d2ed55c37d11fcf83ab475324495a.png)
左下角是用户, 中间红色代表的是 网页, 网页中间 有一个输入框,并且这个输入框有唯一的 ID = abc
页面提交的时候,就会把这个输入框中的用户输入内容 提交给后面的PHP程序 , PHP通过 ID来准确获取 这一个输入框中的内容
到这里 大家可能会有一个疑问了
输入框我明白了, 可是 怎么才能"提交"呢? 嘿嘿问得好
接下来 咱们看这里
```javascript
<input type="submit" value="提交"/>
</form>
```
代码的最后 有一个这样的<input>标签 看到了吗? ^\_^
这个input 的 type=submit 这个叫做 提交按钮
就是页面中 这个
![](assets/1593412471-33af7df616e66debd413add8ac2d3220.png)
^\_^ 这个就叫做 提交按钮, 它点击之后 表单就会执行提交
不过还有一个问题 , 表单提交后 怎么能指定 它到底提交给谁呢?
接下来看这里
在HTML最开头 不是有一行 <form>吗?
```xml
<form action="login.php" method="post">
```
看到了没有 action=login.php
是不是一下子豁然开朗了? 意思就是说 把内容 提交给一个 叫做 login.php的文件 来执行啦
这样我们就很清楚了吧
上篇就说到这里了,大家好好消化一下, 在下篇中, 我们就要正式开始 学习PHP的制作方法啦 ^\_^
@@ -0,0 +1,228 @@
## 12.认识动态网站并学会自己制作第一个动态首页(下篇)
\[PHP对HTML表单内容的获取\]
我们在上篇最后说到, 表单依赖action指向的php文件的位置, 把内容一次性提交过去
```xml
<form action="login.php" method="post">
...
...
</form>
```
有内容的提交,就一定有内容的接收了对不?
看到末尾的 method="post"了吗? 这就提交内容的方法
这里需要注意的是, 在表单中如果提交的方法 设置为post, 那么在PHP中接收的方法 也要一致的设置为post
好了 那么接下来 咱们就开始编写PHP的表单处理代码吧
```php
<?php
echo '服务器类型: '; echo $_POST["servertype"]; echo '<br>';
echo '服务器主机名: '; echo $_POST["servername"];echo '<br>';
echo '服务器域名: '; echo $_POST["serverdomain"];echo '<br>';
$servertype = $_POST["servertype"];
$servername = $_POST["servername"];
$serverdomain = $_POST["serverdomain"];
?>
```
代码如上所示
PHP代码由 <?php 标签起, 到 ?>标签结束, 中间是代码的部分
从代码的第一行后面, 我们可以看到 $\_POST\["servertype"\]
$\_POST 就是PHP获取由POST方法提交上来的参数
那这个参数是什么呢
其实就是 咱们之前在 表单页面上的每一个输入框的ID 所附带的
比如:
服务器类型: <input type="text" list="servertype\_list" name="servertype" />
后的name="servertype" . 这一项 就会最终被 $\_POST\["servertype"\] 所获取到
其他的几个输入框 也都是一样的原理
好了 接下来 咱们把准备好的php代码 写入到一个文件叫做 login.php
再把 上篇中的 HTML的代码 写入到一个文件 叫做 index.php
把这两个php文件,放入你的nginx主目录当中去
(之前你在哪里放的phpinfo文件的地方, 就放到哪里去)
然后 我们来测试一下效果
![](assets/1593412503-47d6eda83b3d11190ef88c407bc73752.png)
表单页面点击提交后, 后面紧接着调用的login.php 就成功的把你输入的内容获取到 并打印在页面上了
到这里,说明我们的PHP 以及PHP代码 工作一切正常
\[PHP和MYSQL\]
上一个单元,我们最终实现了重要的 PHP获取表单内容
这是非常重要的一个步骤,代表着咱们的 后端PHP程序, 和前端HTML页面 可以互通信息啦
不过如果只是简单把 表单当中的 一些字段 获取到,并且打印出来, 这个也没什么实际的用途啊 是不是
所以说 我们来进一步吧
咱们应该有听说过 LNMP这个词吧 对不?
它就是 LINUX NGINX MYSQL PHP的缩写
也就是 最经典网站的动态网站的 搭建组成部分
其中的MYSQL数据库,是必不可少的一个环境
我们接下来希望实现的功能, 就是当表单提交服务器的参数数据后, 通过PHP获取到,然后最终写入到对应的数据库的表中
(在企业中 很多功能都是这样的实现方式, 比如Devops自己用PHP+MYSQL开发一个 资源入库的小软件 等等)
咱们首先来 快速安装一下mysql, 这里直接走yum即可,版本会很旧 但是速度快 且不影响我们的任何使用
yum install mysql-server php-mysql
安装mysql的服务端, 以及php的mysql支持
安装好以后 , 执行
/etc/init.d/mysqld start
mysql安装好以后,默认是空密码登录, 但是我们的PHP程序没办法连接 空密码的mysql
所以 我们必须初始化一下 mysql root账号的密码
用 mysqladmin -uroot -p password 1234 初始化一下密码 随便设置一个 1234作为密码就好了
登录的时候 使用
mysql -uroot -p1234
MYSQL准备好了以后, 还有一个重要的步骤 我们需要插入一下
就是咱们之前安装的PHP环境
之前我们在安装PHP的时候, 编译安装的参数 都是采用默认的 , 但是现在 就必须做出扩展了
因为接下来PHP就要连接MYSQL了, 默认的编译参数是 无法支持的
按照如下的步骤, 回到你的php的下载解压目录中 重新执行编译
```coffeescript
[root@server01 down]# cd php-7.3.1
[root@server01 php-7.3.1]# ./configure -enable-fpm --enable-pdo --with-mysqli --with-zlib
[root@server01 php-7.3.1]# . make & make install
```
覆盖安装后, 重启php进程(直接 killall php-fpm , 再启动)
\[PHP代码的mysql扩展\]
接下来 我们需要把PHP程序 进行扩展
就像上一个单元中说的
把从表单提交过来的数据, 再写入到mysql数据库中去
完整的代码如下
```php
<?php
echo '服务器类型: '; echo $_POST["servertype"]; echo '<br>';
echo '服务器主机名: '; echo $_POST["servername"];echo '<br>';
echo '服务器域名: '; echo $_POST["serverdomain"];echo '<br>';
$servertype = $_POST["servertype"];
$servername = $_POST["servername"];
$serverdomain = $_POST["serverdomain"];
$servername = "127.0.0.1";
$username = "root";
$password = "1234";
// 创建连接
$conn = mysqli_connect($servername, $username, $password);
// 检测连接
if (!$conn) {
die("Connection failed: " . mysqli_connect_error());
}
echo "MYSQL连接成功<br>";
// 创建数据库
$sql = "CREATE DATABASE IF NOT EXISTS CMDB";
if ($conn->query($sql) === TRUE) {
echo "数据库创建成功";
} else {
echo "Error creating database: " . $conn->error;
}
// 创建数据库表
$sql = "CREATE TABLE if not exists CMDB.servers (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
servertype VARCHAR(30) NOT NULL,
servername VARCHAR(30) NOT NULL,
serverdomain VARCHAR(50))";
if (mysqli_query($conn, $sql)) {
echo "数据表 servers 创建成功";
} else {
echo "创建数据表错误: " . mysqli_error($conn);
}
$sql = "INSERT INTO CMDB.servers (servertype, servername, serverdomain)
VALUES ('$servertype', '$servername', '$serverdomain')";
if ($conn->query($sql) === TRUE) {
echo "新记录插入成功";
} else {
echo "Error: " . $sql . "<br>" . $conn->error;
}
mysqli_close($conn);
?>
```
咱们来解释一下上面这段代码
是按照如下这样一个步骤来进行的
1)获取表单的几个参数过来
2)把获取到的参数 再分别放入三个PHP的变量中,预备后面的使用
2)创建到你的MYSQL的链接 (这里注意,写准确你的mysql的位置, 如果是本地就是 127.0.0.1, 端口默认是9000 还有账号密码)
3)创建一个叫做 CMDB的 数据库
4) 在CMDB下 创建一个叫做 servers的表, 并且在表中定义了4个字段,用来存储 表单提交过来的信息
5)把之前存储下来的 三个PHP变量内容,插入到表中
6)关闭mysql 完毕
最后 咱们一起来测试一下结果吧 ^\_^
![](assets/1593412503-f164ce1d1e4ec136f8d823b9eca40e27.png)
```ruby
mysql> select * from servers;
+----+------------+------------+--------------+
| id | servertype | servername | serverdomain |
+----+------------+------------+--------------+
| 1 | 云主机 | 127.0.0.1 | example.com |
| 2 | nihao | 127.0.0.1 | example.com |
| 3 | 云主机 | 127.0.0.1 | |
| 4 | 云主机 | 127.0.0.1 | example.com |
| 5 | 云主机 | 127.0.0.1 | example.com |
+----+------------+------------+--------------+
5 rows in set (0.01 sec)
```
恭喜你 成功啦 ^\_^
感兴趣的朋友 可以自己扩展更多 PHP操作MYSQL的方法
我们的 动态网站就学习到这里了
@@ -0,0 +1,193 @@
## 13.Nginx+Jenkins 学习自动化持续集成(上篇)
\[自动化概念的由来\]
从这一章节开始,我们需要陆续引入几个新的技术,来配合nginx,懂得集群和自动化的概念,这无论对于开发人员,还是运维人员,还是测试人员,都是非常实用的技巧
在一般的互联网企业中,nginx几乎无处不在,为什么呢?
因为从用户端发出的请求,绝大部分都是HTTP请求,不管是浏览网页要好,玩手机游戏也好,只要是走网络,90%以上都是这种情况(这个我们也讲过的)
既然用户端发过来的都是HTTP请求,那么自然,nginx组成的集群就一般会在"前端" 等待着请求的到来
动态网站是怎么回事,我们已经学过了,既然是动态网站,那就一定有程序代码
程序代码一般怎么分布呢?
一般情况下,分为两种情况
(1)最前面一层是负载均衡层,第二层就是nginx+动态代码作为功能层(专业的叫法是:AP层)
(2)nginx和动态代码 都单独分出集群,然后,用nginx统一作为AP层的反向代理
程序代码是会不断更新上线的,而对于一个已经稍有点规模的企业,它的程序代码是不可能靠原始的手动来更新到服务器之上,都是要走一个自动化的流程
什么又是自动化呢? 其实就是把原本手工作业的活儿,用脚本来代替,解放双手提高效率
举个最简单的例子,企业中的LNMP集群,如果是手工作业更新代码的话,大概是如下的流程
1:我们需要先把一台要被更新的后端AP,从nginx负载均衡上摘下来
2:停止AP服务器上的 nginx 和 php(咱们就用php举例子吧)
3:把最新的php代码从git(或者其他版本控制工具)上 checkout下来,然后提交分支
4:把分支更新到这一台后端AP上,并且确保代码版本已经一致
5:开启这一台AP上的各种服务
6:通过手动发送HTTP请求,给这台AP服务器,看看输出是否正常
7:没问题的情况下,把这台AP放回负载均衡
8:重复这一流程,直到所有AP都被更新,并且所有NGINX上的日志 都正常返回200
即便是最简单的一个自动化更新上线流程,都要分这么多的步骤,手工很辛苦是一方面,更主要的是,全手工非常容易在中间出错,所以,自动化是必须必须的
那么最原始的自动化,就是Linux集群上的各种脚本编程了
就目前来说,用的最多的就是 shell 和 python这两种
而脚本怎么去写呢? 脚本具体都要做什么工作呢?
其实就餐考上面的 1-8 类似这样的步骤,把每一步骤都可以做成一个单独的脚本来替代手工执行
通过脚本 实现的最原始的自动化 是处于这样的一个局面 参考下面这张图
![](assets/1593412528-df0cfb9065966182d7eb783c41ede867.png)
不难看的出来,各个脚本虽然从局部 已经把手工作业大大的减轻了负担, 但是 脚本由于数量过多 各自为政,那么运维工程师 在使用的时候
也都是凭着经验 一个一个的去调用,一个一个的去检查结果, 万一中间出现了疏忽 调用了错误的脚本,那么必然会造成不必要的麻烦
(什么叫做持续集成, 为了解决 原始化的脚本 顺序问题, 关系, 验证, 历史纪录,图形化 都可以有效的实现 并且 联合起来 )
这里可能就有学员会问了: 那就干脆再写一个更大的脚本把所有的小脚本都融合在一起不就行了嘛?(量级 还要用 更多的代码 去实现 集成)
这样做不是不行,但是如果提出更高的要求呢? 比如 还希望融合起来的脚本 可以使用图形界面调用,且每一次运行都有明确的记录留下,而且还要求 任务的每一个部分 都可以方便的以接口的方式 和其他相连的部分连接起来呢? 是不是就很难实现了
这里就要给大家推荐一个 可以实现如上要求的一款方便的持续化集成工具 就是Jenkins
当然了,光有Jenkins还不够,还需要加上 自动化配置分发 还有版本控制 =》 最终形成 持续集成
\[实现持续集成,需要的相关技术介绍\]
* bash 脚本编程
这个就不用多介绍了,作为一名运维的基础技能, 不过需要说明的是 bash脚本虽然很灵活简单 但是仅仅适用于
不需要性能的 且是流程化的功能
(提问:bash shell / python )
什么意思呢?
所谓的流程化,指的是比如 拷贝几个文件,执行几个命令 ,导出几个日志等等,这些都不怎么需要性能 就是一次性完成的事情
什么情况就不适合了呢? 比如 分析量大的日志文件内容,循环读取 ,这种情况下 BASH是非常非常慢的
* Python 脚本编程
后面的持续集成中, 凡事流程化的脚本 我们教给bash就好(因为编写简单 且速度不受影响)
而需要性能的地方 主要是集中在 持续集成的过程中,需要有测试功能加入
![](assets/1593412528-c582a97e56dfe4d5622aab022e23f8d2.png)
上面这张图,把bash脚本,和python脚本,各自擅长的领域做了一个介绍
* Jenkins持续集成工具使用
Jenkins 鼎鼎大名的 使用java开发出来的 基于图形化界面的 持续化集成工具
现阶段 只要是达到了中等规模的企业,一般都会优先考虑 引进Jenkins (因为速度快 且节约成本)
本门课程 我们会有一个阶段 单独学习 Jenkins的安装部署,以及中间的很多使用技巧
* LNMP环境 和 开源站点搭建
为什么需要LNMP呢? 持续化集成在企业中的运用,有相当大一部分 是为了统一化线上流程发布
* 测试相关概念
我之前也跟大家提过,运维 测试(白盒测试 python) 开发 铁三角关系 是雷打不动的
测试工程师相关的技术 我们做运维的 也不能不懂
况且在持续集成当中,测试的观念 以及具体的技术 都是必不可少的 不然根本算不上一个完善的 持续集成自动化作业
\[jenkins的基本使用方法\]
jenkins是一个基于java的图形化的工具
请先确保你的Linux上安装了jdk
\[root@server01 ~\]# yum install java-1.8.0-openjdk\*
确保JDK之后,接下来 其实Jenkins安装很简单 如下使用yum即可
(提问过: 什么时候 适合使用源代码编译的方式安装? 只有线上环境 涉及到的各种软件 方便随时扩充编译模块 , 线下环境 监控,自动化平台 )
\[root@centos03 home\]# wget -O /etc/yum.repos.d/jenkins.repo [http://pkg.jenkins-ci.org/redhat/jenkins.repo](http://pkg.jenkins-ci.org/redhat/jenkins.repo)
\[root@centos03 home\]# rpm --import [http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key](http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key)
\[root@centos03 home\]# yum install jenkins
jenkins安装好以后, 可以使用netstats 命令查看 默认的8080端口的java是否开启
![](assets/1593412528-2d30640985f260dce6fac65bbd40e50a.png)
jenkins安装好了以后,service jenkins start 即可, 默认工作在 8080,直接浏览器访问即可
第一个页面 我们可以看到Jenkins提示,需要一个初始密码才能继续配置
下面也提示了,找到红色标记的那一行在Linux当中的配置,黏贴出来 就可以继续
Unlock Jenkins
To ensure Jenkins is securely set up by the administrator, a password has been written to the log (not sure where to find it?) and this file on the server:
/var/lib/jenkins/secrets/initialAdminPassword
Please copy the password from either location and paste it below.
之后 选择suggested Jenkins ,然后就如下图 开始安装了
![](assets/1593412528-7e20c24c56da925be4edc4c09e9d31f8.png)
之后配置你的账号和密码, 然后一路点击即可 很简单
![](assets/1593412528-a3844e830822d38c886c68e98e41aa2d.png)
安装完毕之后,咱们就来到了 Jenkins的首页(登录之后)
来到首页后 别的先别弄,先看中间有一个 create new jobs => 创建咱们的第一个任务
![](assets/1593412528-6d46b563723d51db83bce772bbc2f87d.png)
理解Jenkins当中的这个job 非常关键,它是Jenkins的核心,一切都是一个一个的任务(jobs)
我们接下来先给 第一个job一个名字 , 这里尽量使用英文名字 (中文名字也支持,但是后期不好管理,因为这些创建的jobs都会在Linux jenkins目录下存放 你也可以想象的到)
如下图所示,我们给咱们的第一个job 起一个名字 就叫job1就行了(在下图 第一条红线的地方)
接下来 咱们选择 Freestyle project (一般没有特殊需求 就选择这种类型的job即可)
然后 点击最下面的 save 就OK了
![](assets/1593412528-a9f9b4c71a6ad554d66151c1f522b81b.png)
接下来 我们就可以进入 这个job01的配置界面了
可以看到 一个job中 有很多可以配置 和 添加的地方 ,这里先不着急,就先设置一个最上面的 Description 随便写个简介就好(修改都记得 点击最下面的 save 保存)
![](assets/1593412528-f4a75c0f25591ede537c13f4b6dab24b.png)
其他的地方 先不着急配置,我们先点击Jenkins页面左上角(如下图)回到首页
然后,就可以看到 JOB01 显示在 任务列表当中
估计这里 大家还是没太理解, Jenkins当中 创建这些JOBs 都是做什么用的呢?
关于这个JOB的定义 , 如果咱们的学员以前是做开发的,很可能会理解为, 哦 这个应该就是一个程序代码的工程吧 ??
这样的理解不能说是错误的,因为每个JOB中 确实都需要写代码(bash shell/ python),但是 这个可不像编程中"工程" 那样的复杂 , 比它简单太多了
看一下 下面这张图 给大家展示的内容 主要强调几点
* 首先Jenkins创建的任务(JOB),一般都是在每一个任务中 添加脚本代码(例如 SHELL or Python), 直接运行 也不需要什么编译流程
* Jenkins的这些任务(JOBs) 与 任务之间 都可以通过很简单的方式(图形化的方式) 建立联系 建立关联 设置各种触发条件(parameterized remote trigger) 判断(比如 第一小节所说的 Hadoop任务步骤 都可以很轻松建立关联)
* 每一次任务的运行,都有运行历史的保留,不像你平时手动调用 bash.sh脚本 想留个记录就很麻烦的
其实你也可以想像,我们运维平时写的 bash / python 这些脚本都是命令行形式的
如果想把脚本的调用变成图形化 这得多难啊! 但是有了Jenkins以后,这就非常轻松的就可以实现了
![](assets/1593412528-0560d765a577ef3500f50624425440c2.png)
然后 我们先不着急给一个JOB添加脚本,我们得先给JENKINS安装一些之后会用到的 必要的插件
选择最左边的Manage\_Jenkins ,然后再选择右边的 Manage Plugins(管理插件 : 首次安装jenkins 选择模拟的推荐插件)
![](assets/1593412528-f4485cfed2cb44947656ea83643b5849.png)
之后 就可以看到选择安装插件的界面了, 如下所示 ,多选勾选之后 点击最下面的安装即可
我这里给大家推荐的插件 如下截图中
(请注意,你也可以自行勾选安装别的插件,但是 我这里极不推荐都安装,因为很容易造成Jenkins的不稳定)
![](assets/1593412528-fd7c4c4936eb3d8ae11642477d0e6c59.png)
![](assets/1593412528-9c627e22be1d38e7ee0d7dfbdb75e001.png)
![](assets/1593412528-b7b03098e5f9bd8d15307b3874157371.png)
安装好插件以后,jenkins会在页面下面提示你,它会自动重启来生效
上篇就先学到这里
@@ -0,0 +1,126 @@
## 14.Nginx+Jenkins 学习自动化持续集成(中篇)
GIT版本控制的引入 以及和持续集成的重要关系
首先咱们先要解决的一个问题, 就是 为什么存在版本控制, 以及版本控制的重要的存在意义
![](assets/1593412558-404237b32139283c98f14e6ac308334f.png)
在这一个小节中,我们必须先学习一下 GIT版本控制(CVS SVN)的使用方法 以及和持续集成的重要关系
本小节的任务: 学会GIT的基本概念, 以及GIT服务器搭建方法 ,把我们的持续集成中所有涉及到的脚本代码 以及一些配置文件 都要加入对应的仓库
GIT说白了 是版本控制的一种 而所谓的版本控制, 其实也就是 通过一种方式 把代码以及配置文件 有效的管理起来 有迹可循
\[GIT大体的工作模式\]
![](assets/1593412558-e97a72e60de8f936c64c220ff759c7fc.png)
![](assets/1593412558-2eb098b6f6f8ed56f91ab11a0a7edc6c.png)
如上图所示, GIT的基本工作原理
首先先要在服务端搭建GIT服务, 有了服务后,在创建一个仓库, 称作GIT远程仓库
用户通过 clone的方式,把远程仓库 拉取到本地一份, 变成本地仓库 ,两边在刚拉取下来的时候 是完全一致的
远程一个仓库 其实就是一个目录,仓库clone到本地后,也是同名称的 目录
用户在本地仓库所在目录中 不过执行什么操作(新增文件,修改文件,删除文件)其实都是跟GIT关联的,处于版本控制之下
仓库在刚刚创立时为空,用户需要先把本地仓库充实起来(新增我们需要的文件 进入版本控制),然后同步本地库 =>进而同步远程库
对于同一个远程仓库来说,任何一个其他的用户 都可以clone到自己本地,进行自己的修改 再推送
![](assets/1593412558-630d34ab5d1c7dd2106cf448d9c1ee10.png)
\[企业中 运维是怎么用GIT的(服务融合版本控制)\]
![](assets/1593412558-ac6bcc87304b4ab2de0f33d0b9d8e05c.png)
![](assets/1593412558-3ca707123323d3baad0f1ed4dd02fa68.png)
如上图所示,公司运维工程师 通过GIT 将线上NGINX配置文件 加入版本管理
管理的具体内容,其实就是 /etc/nginx/ (注意 这里是通过yum安装的nginx)下面的全部配置文件
GIT通过创建branch分支(一个分支 就是一个副本 一个镜像 互相独立), 把 线上生产NGINX集群 和 一个小的 stage环境 分开
production 生产环境集群 是真正处理线上用户请求的 核心环境 , 这里配置文件是取自 MASTER分支
stage 测试环境 是最小量的 ,一般没有用户请求,但是有一样的服务搭建(本例中 就是NGINX),用于任何改动下的 初步测试, 这里的配置文件 取自DEV分支
b
企业中并不是代码才需要版本控制,配置文件也一样需要, 只要是任何有可能被改动的文件,能引起故障的,都要受到版本控制的管理
当运维想修改 NGINX配置文件的时候,先把远程库NGINX.repo clone本地, 在dev分支中 任意修改 (或者 也可以直接登录 stage机器 在/etc/nginx/下, 确保在dev分支下 修改)
修改好以后,提交DEV分支,并在 stage机器上 拉取下来, 测试DEV分支 是否让nginx正常
测试没问题后,把DEV的分支新增加的内容,合并进入master分支(git merge , DEV:commit -> master)
之后,production环境把 master分支 同步到每一台nginx上
这里有几个隐藏问题:
1) master分支被更新后,如何让30台prod nginx 都更新呢?能想出几种方法?
```plain
30 => host.list
for i in `cat host.list`
do
ssh "$i" "cd /etc/nginx/ ; git branch | grep master ; git pull ; nginx -t ; server nginx reload"
done
```
2)如果发布production后,出了严重的问题,怎么立刻解决?
```perl
GIT 回退, GIT本地仓库的回退 -> 本地文件也回退
git log --oneline -5 # git commit id
git reset --hard commit_id
```
3)如果在 30台 nginx 上 , 切换了分支 会发生什么?
```coffeescript
/etc/nginx/ ; git checkout dev
```
\[企业中 开发结合持续集成 是怎么用GIT的(版本发布的 起始点)\]
![](assets/1593412558-b7d624ea7febb15b1dbf64ed3315e3ef.png)
如上图所示, GIT 其实是作为 企业 持续化集成发布的 触发点
开发人员写好代码后,先提交测试分支,然后在单元测试服务器上 跑基本功能测试
没有问题后,合并测试分支 进入 主分支
持续化集成平台 是以任务和任务之间 先后顺序调度的形式 进行工作
先有第一个任务,实时检查master分支的更新,只要有了更新 就触发下一个打包任务
所谓的打包,是将开发人 希望发布的代码文件(也可能包括 开发配置文件等等)统一打成一个Linux包(RPM/DEB)
包 需要附带 人名,日期,内容
每次更新都要打一个包,但是并不一定是 每个包 都发布生产
需要发布的时候,先安装包 在灰度发布(stage 或者 preprod)
stage一般代表 没有流量的 简单功能测试 服务器
preprod 代表有部分生产流量的 小规模集群
然后 白盒测试人员开发的程序 (代码输入输出测试 + 压力测试) 对 两个环境进行评估
Ok->继续发布生产 , failed -> 回退灰度环境
发布生产后,需要再接上测试任务,如果失败 要全部回退
通过上面的学习内容 我们知道了 版本控制对整个研发团队至关重要 (其实对运维和测试 也很有用)
那么咱们为了 让公司的研发团队尽快使用上GIT 运维的任务就要来了…
想用GIT 我们就得先有GIT服务 接下来的GIT\_Server的搭建方式 会很好的帮助大家 尽快在企业中把GIT服务在最短时间内 搭建起来 让开发兄弟们 点赞哦
@@ -0,0 +1,742 @@
## 15.Nginx+Jenkins学习自动化持续集成(下篇)
\[git 服务器的搭建\]
— Git daemon 最轻型 git 服务器搭建
其实GIT服务器有简便又快捷的安装方式,这就是git-daemon.
并不一定要依赖类似什么github,gitlab之类的平台
如下操作:
```plain
yum install git git-daemon # (直接yum安装就好 很简单)
```
接下来我们创建一个 空目录 作为 git server端的 仓库存储路径 并且给予权限
```coffeescript
[root@server10 ~]# mkdir -p /data/git/repo/
[root@server10 ~]# chmod 777 -R /data/
```
然后 我们通过 git daemon 启动进程
附加detach参数 : 是为让 git daemon以守护进程的形式 运行在后台
附加enable=receive-pack 是为了 开启 git daemon的
```coffeescript
[root@server10 ~]#
git daemon --detach --verbose --export-all --base-path=/data/git/repo/ --reuseaddr --enable=receive-pack /data/git/repo/
[root@server10 ~]#
```
之后 我们就可以看到 一个默认端口工作在9418的 git daemon小型服务器就起来了
```ruby
[root@server10 ~]# netstat -tnlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:9418 0.0.0.0:* LISTEN 2319/git-daemon
```
接下来 我们进入刚刚创建的 仓库目录
再建立一个子目录 scripts.git (每一个不同的repo )
(上面的 /data/git/repo 算是 git daemon server 的根目录, 根目录下的 子目录是对应每一个需求所建立的单独的repo仓库)
初始化一个 git版本库
```coffeescript
[root@server10 puppet.git]# cd /data/git/repo/scripts.git
[root@server10 puppet.git]#
[root@server10 puppet.git]#
[root@server10 puppet.git]# git init --bare #(初始化)
初始化空的 Git 版本库于 /data/git/repo/scripts.git/
[root@server10 scripts.git]#
[root@server10 scripts.git]# ls
branches config description HEAD hooks info objects refs
git config daemon.receivepack true
[root@server10 scripts.git]# git config daemon.receivepack true (这一步很重要 很多网上的文档说 git daemon时候加上—enalbe=receive-pack 就可以git push了, 但是实践证明 最后不做这一步 客户端还是不能push )
```
有了这些文件之后 , git daemon的服务端 我们就准备好了
—接下来 我们找一台 服务器当作 GIT 客户端
1) 执行git clone 从新建的服务端 把 .git 空仓库 拉下来
```php
git clone git://server01/scripts.git
Initialized empty Git repository in /opt/hadoop/scripts/.git/
warning: You appear to have cloned an empty repository.
```
2) 然后 进入这个 clone下来的目录 查看一下 远端的git信息
```ruby
[root@server01 scripts]#
[root@server01 scripts]# git remote -v
origin git://server01/scripts.git (fetch)
origin git://server01/scripts.git (push)
```
能看到远端的git仓库信息,就表示工作正常了
\[puppet server和持续集成的关系\]
puppet 是当今作为一名高级运维的必备技能,运维线下集群自动化的鼻祖, 发展到今天 puppet已经是作为一个企业中衡量运维线下集群管理自动化的其中一项很重要的指标,很多公司 面试的时候 直接就以puppet作为标准来衡量候选人的自动化理念是否合格
大米所经历的10几家企业中 有 7-8家企业都使用puppet做配置分发管理 (puppet范围 管理全部的运维)
puppet的年头也同样不短了, 互联网上各种关于puppet的学习资料 满天飞
不过 一般的学习资料 都是比较零散的 且逐个一一 介绍puppet的各种语法细节 和资源定义
![](assets/1593412590-60e428a5c3195304067bf6f448781db3.png)
我们看一下 上面这张图
其实 puppet 是一个基于ruby语言开发的 使用自有的puppet描述语言
来实现 管理配置文件、用户、cron任务、软件包、系统服务等 这样一款实用配置分发管理技术
puppet 的工作模式 也是基于 C/S模型的服务(服务端 - 客户端),
服务端是 puppetmaster -> 负责定义和配置任务
客户端 puppet -> 即是一个命令 也是一个服务service: 从puppetmaser主动拉取 服务端是被动的(面试中 经常被问到 puppet是主动拉取 还是推送)
puppet可以实现的功能很丰富 不过其最经典的 还是对集群的服务和配置文件的管理 (如上图中画的这样)
如上这样的定义 看上去还是有些笼统 我们接下来用更实际的例子 来说明 puppet到底做什么用的
2) Puppet对于Linux运维工程师来说 它的重要意义
上面我们介绍了puppet的基本概念
接下来 咱们来看看puppet 在企业运维中 到底是如何发挥效用的
首先我们假设一个环境
企业中 有200+台服务器 全部安装Centos x64
运维在工作中 一个很基本的内容 就是管理所有服务器上的 用户账号(企业内部的员工)
说到用户账号 那么Linux服务器 用的最多的 自然也就是 通过 ssh-key的方式 做登录认证
ssh-key的公钥 是需要被推送 到所有相关的服务器上的
那么假如 我们没有puppet这种配置分发管理技术 实用最原始的方法 如何来实现 这些ssh-key的推送和管理呢?
那么最原始的方法 自然也就是 运维写一个 bash 循环脚本
例如这样
```ruby
for i in `cat serverlist`
do
hostname="$i"
scp userdir/keynames*.pub $hostname:/tmp ; ssh $hostname "cat /tmp/keyname*.pub > ~/.ssh/authorized_key "
done
```
\--
这样子 原始的方法 运维自动化管理的方法
第一 太low
第二 很不灵活
第三 不自动化
当然这里也要说明一下 (bash + Jenkins持续集成) :
举个例子
一般来说 企业中(特别是那些 运维安全方面 规范比较严格的公司)
对于 用户管理 sshkey的 还是很严格的
每个员工都必须有自己单独的ssh-key
每个员工只能在 部分和自己工作内容相关的服务器上 有ssh-key登录的权限 (mysql DBA)
一般除了 运维以外(运维是最高权限)其他任何员工 都不能有root的登录权限 su - root (使用sudo限定 cmd)
一旦有新员工入职 或 老员工离职 那么需要运维在很短的时间内 就将 ssh-key部署到集群中生效(卸载或者安装)
那么这么多的要求 如果我们运维只使用这种 原始的shell脚本的话
不是说不能实现 但是实在是太耗费没有必要的经历
有了puppet以后
对于上面举出的这个sshkey例子 就变得井然有序了
puppet的 专属ssh模块module 可以自行定义
有哪些员工 分别登录什么机器 都可以被编入模块
模块中 还可以划分子模块 -> 类和子类(之后会讲到) 这对同一个集群的 不同用户权限的划分 非常实用
另外 一旦员工的ssh-key发生了改变,那么puppet会自动发现 并通知 puppet-客户端主动拉取更新 不需要人为干预
等等...
仅仅这一项ssh-key的功能 就能帮运维团队 节省下大量的时间和精力
那么 通过这一段的讲解 我们对puppet在企业中能起到什么样的作用 也有了一定的了解了
那么puppet这么实用的工具是用什么语言开发出来的呢?
我们看后续章节
3) Ruby是一种什么样的语言 用途在哪里
puppet ruby
我们来看一下 Ruby的定义:
Ruby是一种纯粹的面向对象编程语言,一种通用的 开源的、解释的编程语言。
关于面向对象和开源的概念 咱们不需要过多解释了
那么关于什么是 解释型的编程语言 在大米运维的高级服务篇 tomcat章节的 编程理念部分 有详细的介绍
另外 对于Ruby还有一个比较潮的称呼 有很多人叫它 : 魔幻语言
根据语言本身的设计风格和拥护者的气质也可以把语言划分成“魔幻语言”和“简约语言“两类 (如下这段话来自其他博客评论)
所谓”魔幻语言“,主要代表作品有C++、Perl、Javascript和Ruby。
这些语言拥有丰富的特性,聪明的技巧和意想不到的奇效,永远有发掘不完的技巧妙用
反过来,“简约语言”崇尚清晰直接 C、PHP、Python和Lua这一派语言的代表作。
够用就行,要求从代码容易理解,宁可笨一点、累一点、多写一点代码,反对出人意料的技巧,反对故弄玄虚。
Ruby在目前阶段 还是处于一个新型的开始
有哪些应用领域呢?
准确的说ruby适合系统管理,和unix的shell结合起来用,系统管理员的最爱
ruby语言本身可以实现日常工作的几乎任何功能,非常适合做测试语言 (puppet unit test )
ruby on rails可以开发网站
咱们课程的puppet 也是源自ruby语言 继承了ruby的这种魔幻特性 功能伸缩性很广
除此之外 我本人也经历过 企业用ruby开发一些特殊的 前沿的产品 比如:人工智能 人脸识别 等等…
对于 ruby这门新型的活跃语言 我们就介绍到这里
需要提醒大家的是
puppet是基于ruby开发的 但是 我们日常工作中 所写的puppet code并不是ruby 而是一种 puppet自身的表示行语言 其写法比起脚本语言来说 更加简洁快速
4) Puppet基本语法 以及资源的定义方法
我们直接拿企业中 puppet自动化管理的一段 代码来演示一下
```php
class cron{ #定义一个类叫motd
package{ 'crontabs': #定义package资源
ensure => present, #要求setup这个包处于被安装状态
}
cron{node_exporter_jobs_1:
command=>"cd /usr/local/node_exporter; bash /usr/local/node_exporter/node_exporter_shell.sh”, #
user=>root,
minute=>'*',
require=>Package["crontabs"];
}
}
```
如上就是 一段取自企业中 用来定义cronjob的一小段puppet codes
个人总结看来,puppet语言形式上的特点就是“花括号分类,冒号声明,逗号结束
从上面这一段代码 我们可以看得出来 puppet自身的语言 其实是一种结果形式的
严格来说 算不上是编程 只能说 一种配置的定义 或者说 用类似脚本的形式 定义需要被配置的资源 <=修饰
比如
```css
cron {
}
```
定义一种资源 这个资源的叫做 node\_exporter\_jobs\_1名字
这个资源 其中的内容 其实就是 crontab job内容
然后 我们再来看 下面这两个例子
![](assets/1593412590-9706114c68245b83616450a0e04e1f87.png)
我们对 puppet语言 大概什么样子 已经有了一个初步了解
为了 进一步掌握它的实际使用
接下来 我们就要进入 安装配置流程了
5) Puppet服务端 客户端的安装 (重要)
为了更深入的学习 咱们先得把 它搭建起来 才行哦
我们之前已经提过了 puppet 也是基于 C/S 模型的
那么咱们从 安装puppetmaster开始 (服务端)
配置实验环境:server01(服务端) server02:(客户端)
在安装puppet之前
我们必须先保证 2台服务器的 FQDN (完整机器名) 已经配置妥当
并且 两台机器之间 可以使用对方的域名 直接ping通
```plain
/etc/hosts
/etc/hostname
/etc/sysconfig/network
reboot
```
这个非常重要 如果FQDN设置的有一点点不对, 那么puppet最终运行都会有问题 (如下这段话 来自其他文章)
Puppet 要求所有机器有完整的域名(FQDN),如果没有 DNS 服务器提供域名的话,可以在两台机器上设置主机名(注意要先设置主机名再安装 Puppet,因为安装 Puppet 时会把主机名写入证书,客户端和服务端通信需要这个证书),如果 已经配置了DNS服务器,那就不用改hosts了,如果没有就需要改hosts文件指定。
(这也是 我们在之前的第一阶段的系列课程中 为什么反复强调 FQDN的重要性 这里就是一个 典型的例子)
我们来预览一下 如果FQDN设置有任何不对的地方 会带来多少问题.. 如下图
![](assets/1593412590-67c1a416b9b1dc1817993dcafdbf5312.png)
FQDN确保万无一失后 我就可以进入安装 puppet环节了
puppet的安装 大米推荐使用yum安装即可
服务端的安装:
puppet不在CentOS的基本yum 源中,需要加入 PuppetLabs 提供的官方源:
```coffeescript
[root@master ~]# wget http://yum.puppetlabs.com/el/6/products/x86_64/puppetlabs-release-6-7.noarch.rpm
[root@master ~]# rpm -ivh puppetlabs-release-6-7.noarch.rpm
[root@master ~]#yum update
[root@master ~]# yum install puppet-server
[root@master ~]# chkconfig --list |grep puppet
[root@master ~]# service puppetmaster start (pupetmaster , puppet )
```
![](assets/1593412590-cf1e56b26ee98a4303169bf860ba1316.png)
服务端端口 看到一个 ruby进程工作在 8140端口后 即可
接下来 就是安装客户端 第二台机器上 (注意 不要把 服务端和客户端 放在一台机器上测试 会有很多怪异的问题出现)
在clients上安装puppet客户端
客户端的安装 其实仅仅只是 安装一个puppet命令而已
另外 还安装了一个 puppet service 这个是个守护进程 运行在客户端后台
时时监测 服务端的配置修改 然后主动拉取
```ruby
wget http://yum.puppetlabs.com/el/6/products/x86_64/puppetlabs-release-6-7.noarch.rpm
rpm -ivh puppetlabs-release-6-7.noarch.rpm
[root@node1 ~]# yum install puppet
[root@node1 ~]# chkconfig --list |grep puppet
puppet 0:off 1:off 2:off 3:off 4:off 5:off 6:off
[root@node1 ~]# chkconfig puppet on
[root@node1 ~]# chkconfig --list |grep puppet
puppet 0:off 1:off 2:on 3:on 4:on 5:on 6:off
```
![](assets/1593412590-be102921cbb0cf7ba703745072f278cc.png)
![](assets/1593412590-948d367d232297120bedd0c2a9546951.png)
我们看下上面这张图 表示了一个 puppet客户端 到 服务端认证的一个流程
每一个新安装的 puppet客户端(一台服务器) 都需要走这个流程 把自己注册到puppetmaster(服务端)中 之后才能正常建立通信并运行
接下来 我们来看一下 具体的做法
确保 客户端和服务端直接 可以互相使用域名
```javascript
服务端
/etc/hosts => 192.168.56.105 server05.dami.com server05
客户端
/etc/hosts => 192.168.56.102 server02.dami.com server02
ping server05.dami.com
ping server02.dami.com
```
3) 保证 所有服务器的时间 都已经同步 ! 重要!!
puppet => hostname + time
ntpdate + cronjob实现 时间同步
如果 任何一台服务器 时间没有同步 认证注册的时候 puppet都会爆出各种 稀奇古怪的错误!(缺点)
4) 然后 我们配置客户端
vim /etc/puppet/puppet.conf(客户端 服务端)
这个是客户端配置文件
在最后 确保有这么3行的配置
```makefile
certname = server05.dami.com # (这是作为 自己的签到 名字 跟自己的FQDN一样即可)
server = server02.dami.com # (这是 连接哪个 puppetmaster 作为自己的服务端 和认证端)
runinterval = 60 # (多长时间 主动拉取一次 从服务端)
certname = server05.dami.com
server = server02.dami.com
runinterval = 60
```
5) 准备好如上这些步骤后 我们发起puppet客户端的注册
![](assets/1593412590-234dca322bee303e5d10cbca2e9a4e33.png)
再次强调一边 这时候注意 不要开启 server puppet start !!!
我们需要先保证手动执行通过
在客户端 使用 puppet agent -t (一次性向 服务端尝试拉取一次 如果 是新装的客户端 这里也就先发起 注册申请)
这一步 没有问题后 我们去到 服务端
![](assets/1593412590-7a2776a808ed249045bae62bf712381c.png)
![](assets/1593412590-a9395fc15f02761a322d81141d1c04a7.png)
这里没关系 我们下一个章节 会以企业中的实际案例 来解释 如何 开始 服务端的puppet 配置写法(也就是 puppet语言的开始)
7 ) 企业实例一 定义 puppetmaster 的manitests 设置集群节点 (puppet代码的开始)
第一步:设置 node节点的定义 (把每一个puppet客户端 都称作一个node 并分别定义在 puppet服务端代码文件中)
我们回到服务端
找到这个路径
cat /etc/puppet/manifests/site.pp
这个就是 puppet服务端 我们要学习的第一个 puppet代码文件
site.pp =》 头节点的定义
在 site.pp中 企业中定义的内容如下
```php
node 'web6.company.com'{
include basic
include ssh
include cron
include firewall
include php
kernel::sysctl {'web6.company.com':
tcp_max_tw_buckets => 5000 ,
tcp_max_syn_backlog => 1000 ,
}
php::php-fpm {'web6.company.com':
pm_max_children => 400 ,
pm_start_servers => 100 ,
pm_min_spare_servers => 30 ,
pm_max_spare_servers => 100 ,
pm_max_requests => 2000 ,
}
}
node 'web7.company.com'{
include basic
include ssh_web4578
include cron
include firewall
include php
kernel::sysctl {'web7.company.com':
tcp_max_tw_buckets => 5000 ,
tcp_max_syn_backlog => 1000 ,
}
```
上面 标红的部分 node ‘xxxx’
就是 定义一个新的 客户端节点 (使用FQDN)
还记得上面 当咱们配置好 客户端认证后 执行puppet agent -t的时候
报出一个
![](assets/1593412590-8592521afa3b662b2d21336014f13d09.png)
这里的错误 其实指的就是 这台客户端 在 site.pp中找不到 这么一个 node+FQDN的定义
然后再往后 蓝色的部分 下面
```makefile
include basic
include ssh
include cron
include firewall
include php
```
这里是 给这一个node节点(客户端) 都推送具体什么内容
include + 模块的名称/类名称 (什么是模块 什么是类 class 我们之后会讲到)
在这里 我们就认为 include basic => 给这台客户端需要推送 一个叫basic的模块 (其中定义了很多 Linux的基本配置)
include ssh => 给这台客户端需要推送一个 叫ssh的模块 (专门定义 ssh配置的 模块 比如 ssh-key有哪些 等等 我们之前也举过这个例子)
8) 企业实例二 定义 puppetmaster 需要的模块(module) 模块是 企业中 puppet实用的最重要的核心之一
上一个阶段中 我们提到了 模块的概念
接下来 我们来细说
```makefile
include basic
include ssh
include cron
include firewall
include php
```
我们就拿 include ssh来说吧
我们在服务端 先找到 /etc/puppet/modules/ 这个路径
模块就定义在这个下面
每一个模块 需要一个单独的目录
一个模块 是某一项功能的推送
![](assets/1593412590-079e0a354dfc3f98aa2649545d5a07a8.png)
manifests/ 是这个模块的 puppet\_code (功能代码)
也就是 这个模块都有什么内容 以什么方式推送给客户端 都在这里定义
files/ 是资源目录 。
任何在 manifests/ 中定义的puppet代码 ,其中如果需要发送一些 文件file{}(比如发送一个ssh-key.pub文件 过去)
那么 这个文件就需要 放入这个files/目录下 算资源的存储
接下来 咱们看下 manifests/ 下面都有什么
```php
cat /etc/puppet/modules/ssh/manifests/init.pp
class ssh{ #定义一个类叫motd
package{ 'openssh-server': #定义package资源
ensure => present, #要求setup这个包处于被安装状态
}
file{ '/home/user01/.ssh/authorized_keys': #定义file资源
ensure => present, #要求file文件处于存在状态
owner => 'user01', #要求file文件属主为root
group => 'user01', #要求file文件属组为root
mode => '0644', #要求file文件权限为644 modules/ssh/files/authorized_keys
source => "puppet://$puppetserver/modules/ssh/authorized_keys" ,
require => Package['openssh-server'], #要求文件被配置之前先执行package资源
}
}
class ssh{
package{ 'openssh-server':
ensure => present,
}
file{ '/home/user01/.ssh/authorized_keys':
ensure => present,
owner => 'user01',
group => 'user01',
mode => '0644',
source => "puppet://$puppetserver/modules/ssh/authorized_keys" ,
require => Package['openssh-server'],
}
}
```
关于上面的 这一段 ssh模块的定义 咱们来拆分讲解
首先 manifests/ 下 至少要存在一个 init.pp (作为起始模块的定义)
init.pp中 最少要存在一个 类(class)
在上面的例子中 就是 class ssh {}
puppet中的类 是一块功能的/资源的 集合
这个集合 可以被 其他地方二次调用 ( 比如 : site.pp的 include ssh 就是这样调用)
puppet在定义出第一个类之后 , 就可以开始 在这个类下 {} 添加我们具体的puppet代码内容
下面是类中的 第一个内容 package{} 是 定义一个包资源
这里的意思是 确保 openssh-server这个rpm包 存在于客户端上
```perl
package{ 'openssh-server': #定义package资源
ensure => present, #要求setup这个包处于被安装状态
}
```
下面 是类中的 第二个内容
file {} 是定义一个文件资源
创建 '/home/yd/.ssh/authorized\_keys’ 文件
确保文件的各种 属性
以及文件具体的内容是什么 source => "puppet://$puppetserver/modules/ssh/authorized\_keys" ,
红色的部分 其实 对应的就是 files/下的 资源文件 authorized\_keys
需要 把这个 真正要被推到客户端的 authorized\_keys 文件 , 事先存放到 files/ 目录下
```php
file{ '/home/root/.ssh/authorized_keys': #定义file资源
ensure => present, #要求file文件处于存在状态
owner => ‘root', #要求file文件属主为root
group => ‘root', #要求file文件属组为root
mode => '0644', #要求file文件权限为644
source => "puppet://$puppetserver/modules/ssh/authorized_keys" ,
require => Package['openssh-server'], #要求文件被配置之前先执行package资源
}
```
这样 我们就定义好了一个 ssh{} 模块
接下来 咱们实际演示一下
9) 企业实例三 引入 puppet类以及子类 (对于企业集群细分 非常实用)
我们先来看一个 企业中 puppet子类的定义示例
```plain
modules/elk/
ELK
class elk{ #定义主类 叫ELK
file{ "/tmp/filebeat-1.2.3-x86_64.rpm":
ensure => present,
mode => 644,owner => root,group => root,
source => "puppet://$puppetserver/modules/elk/filebeat-1.2.3-x86_64.rpm",
before => package["filebeat"],
}
file{ "/etc/pki/tls/certs/logstash-forwarder.crt":
ensure => present,
mode => 644,owner => root,group => root,
source => "puppet://$puppetserver/modules/elk/logstash-forwarder.crt",
before => file["/tmp/filebeat-1.2.3-x86_64.rpm"],
}
file{ "/etc/filebeat/filebeat.yml":
ensure => present,
mode => 644,owner => root,group => root,
source => "puppet://$puppetserver/modules/elk/filebeat.yml",
#subscribe => package["filebeat"],
}
file{ "/etc/filebeat/conf.d":
ensure => directory,
mode => 755,owner => root,group => root,
#after => package["filebeat"],
}
package{ 'filebeat': #定义package资源
ensure => present, #要求setup这个包处于被安装状态
provider => rpm,
source => "/tmp/filebeat-1.2.3-x86_64.rpm",
before => file["/etc/filebeat/filebeat.yml"],
}
}
class elk{}
class elk::nginx {
file{ "/etc/filebeat/conf.d/nginx.yml":
ensure => present,
mode => 755,owner => root,group => root,
source => "puppet://$puppetserver/modules/elk/nginx.yml",
}
exec { "filebeat-restart":
command => "/sbin/service filebeat restart",
provider => shell,
subscribe => File["/etc/filebeat/conf.d/nginx.yml"],
refreshonly => true
}
}
class elk::apache {
file{ "/etc/filebeat/conf.d/apache.yml":
ensure => present,
mode => 755,owner => root,group => root,
source => "puppet://$puppetserver/modules/elk/mq1.yml",
}
exec { "filebeat-restart":
command => "/sbin/service filebeat restart",
provider => shell,
subscribe => File["/etc/filebeat/conf.d/mq1.yml"],
refreshonly => true
}
}
```
上面 是一大段 企业实际的puppet code
本身是一个 ELK module ,用来给客户端推送 ELK所需要的 日志采集客户端
(ELK分布式日志 后期会在 大米运维课堂 的第四大阶段 推出 , 目前第二阶段无)
在这个模块中, 定义了一个主类 两个子类
分别是 从上到下 elk {} , elk::nginx{} , elk::apache{}
elk {}是主类
elk::nginx and apache 是两个子类
在puppet代码中 子类是主类的 一个分支
为什么这里 必须要用到 子类的功能呢?
因为 elk 本身这个模块 是用来给 客户端推送数据采集软件的(filebeat)
elk{} 主类: 是负责一个基本的推送 也就是安装 filebeat到所有 调用 elk{}的 node节点(在 manifests/site.pp中定义)
不管 是哪一台服务器 都需要使用 ELK的日志收集
```css
elk{}
elk::nignx{}
elk::apache{}
```
定义两个子类 是因为: 对于 nginx集群的服务器来说 elk需要采集的是 nginx的特定位置和格式的 日志文件
对于 apache集群的服务器 也是一样的
所以 不同的两个集群 通过 elk{} 主类 先把最基本的 ELK客户端装上 这儿没有区别
然后 不同的两个集群 针对自己 日志格式和位置的不同 分别用 子类 定义自己集群的单独 ELK客户端配置文件
所以 子类在这个地方 就发挥了 很好的作用
另外 : 在这儿 再给大家举一个 企业中 曾经因为 没有使用 子类的功能 很糗的事
企业中 ssh模块配置的一个例子
```plain
class ssh{}
class ssh::log{}
class ssh::apache{}
puppetmaster
puppet]# ls modules/ | grep ssh
ssh
ssh_log
ssh_apache
ssh_mysql => DBA ssh-key
```
这里看到了么 企业中 集群不止一个
不同的集群之间 (甚至 同一个集群中 不同的机器)
都有可能需要对 分发的
由于没有使用 子类的功能
针对不同的集群的ssh需求的不同
只能是 分别 定义多个 ssh模块 来实现
![](assets/1593412590-e75e5df79ae380de4484ad69ac87edd4.png)
我们的持续集成和puppet就讲这么多
+231
View File
@@ -0,0 +1,231 @@
## 16.Nginx和Python白盒测试
# \[什么是白盒测试\]
提到测试工程师,我们不陌生, 不过测试到底是什么含义呢?
其实测试的基本原则 就是检验I/O (输入输出 针对代码 或者 是针对产品功能)
比如 LNMP环境的创建,以及后续的开源站点部署,站点部署好以后,用户的访问就是input(输入),结果的返回就是output(输出)
接下来 我们把检测一个LNMP环境 以PYthon来做, 并且加入持续集成中
不管是安装还是部署,只要是有了改动 就一定需要经过测试,不是说放上去了就万事大吉了
(如果是真实的企业环境部署 一旦出现纰漏 都有可能直接引起线上使用问题)
这就需要测试了,而测试工程师 一般分为 黑盒/白盒测试
黑盒就是外围的应用功能测试(APP bug fix, QA 逐一进行应用测试 模拟用户的使用流程)
白盒指的是代码和系统级别的测试(要求必须熟练掌握 一定的脚本变成 , 对代码的流程 需要有一定的认知)
在我们的持续集成课程中,出马的自然是白盒测试咯,因为我们需要对安装的环境,部署的站点 进行测试(后续 还有增量的代码更新 也需要测试)
而白盒测试使用的语言 可以说Python为主 因为大多数情况下 测试的量较多 开发速度也得快 对性能有要求 (Java)
接下来咱们就来模拟一下 持续集成中 白盒测试做的工作
我们先得安装python3.x (Centos 默认都只是自带 python 2.x )
[https://www.python.org/downloads/source/](https://www.python.org/downloads/source/) 下载python3.x
# \[python\]
![](assets/1593412617-80529d01d73a04d7bba97f7dc32cf4cd.png)
`[root@server01 down]# wget https://www.python.org/ftp/python/3.7.0/Python-3.7.0.tgz`
选择 source tarball
接下来我们进行安装 (我们的安装方法 是2.x 3.x 共存的 安装在 /usr/python/ 避免覆盖)
# 先提前安装好如下的 依赖包
`yum install libffi-devel -y ; yum install gcc-c++ zlib zlib-devel -y`
# 接下来 我们解压Python3.7 并安装
```ruby
[root@server01 down]# cd Python-3.7.0b3
./configure --prefix=/usr/python
make ; make install
```
如上即可
安装好以后 路径如下
`/usr/python/bin/python3.7`
做一下软连接, python3 python2 都有了
```ruby
cd /usr/bin/ && /usr/python/bin/python3 python3
```
上面的python3的安装 , 基于Linux下 (server01 -> Jenkins server)
安装一下 jenkins的两个 python插件
![](assets/1593412617-4414425e850735ccf8ea09c74c9865b5.png)
python 脚本 我们添加如下
```python
#! /usr/bin/python3
import os
def remote_check(hostname):
str1=hostname
#print (str1)
cmd_line="sudo ssh" + ' ' + str1 + ' ' + "netstat -tnlp"
print ("run cmd is :" , cmd_line)
cmd_re=os.popen(cmd_line).readlines()
if 'nginx' in str(cmd_re):
print ("NGINX IS RUNNING")
else:
print ("NGINX RETURN IN NOT RUNNING!!!!")
os._exit(1)
if 'php-fpm' in str(cmd_re):
print ("PHP IS RUNNING")
else:
print ("PHP RETURN IN NOT RUNNING!!!!")
os._exit(2)
if 'mysqld' in str(cmd_re):
print ("MYSQL IS RUNNING")
else:
print ("MYSQL RETURN IN NOT RUNNING!!!!")
os._exit(3)
def remote_check_php(hostname):
str1="sudo ssh" + ' ' + hostname
print (str1)
cmd_line=str1 + ' ' + "curl -q http://127.0.0.1:8080/index.php | grep -i -o --color 'php version 5.6'"
print ("run cmd is :" , cmd_line)
cmd_re_1=os.popen(cmd_line).readlines()
print (cmd_re_1)
if 'PHP Version 5.6' in str(cmd_re_1):
print ("PHP index.php IS FUNCTIONNING")
else:
print ("PHP index.php IS NOT OK!!!!")
os._exit(4)
def remote_check_web(hostname):
str1="sudo ssh" + ' ' + hostname
print (str1)
cmd_line=str1 + ' ' + "curl -q -I http://127.0.0.1:8080/php/wstweb/ | grep 'HTTP/1.1 200 OK'"
print ("run cmd is :" , cmd_line)
cmd_re_1=os.popen(cmd_line).readlines()
print (cmd_re_1)
if 'HTTP/1.1 200 OK' in str(cmd_re_1):
print ("PHP WEB IS FUNCTIONNING")
else:
print ("PHP WEB IS NOT OK!!!!")
os._exit(4)
def remote_check_curl_100(hostname,port):
curl_str1="curl -q -I "
curl_url="/php/wstweb/"
cmd_line=curl_str1 + hostname + ':' + str(port) + curl_url
print ("run cmd is :" , cmd_line)
HTTP_CODE_LIST = []
HTTP_CODE_200_SUM = 0
for i in range(0,100):
cmd_re_1=os.popen(cmd_line).readlines()
if 'HTTP/1.1 200 OK' in str(cmd_re_1):
HTTP_CODE_LIST.append('200')
#print (cmd_re_1)
for k in range(0,100):
if HTTP_CODE_LIST[k] == '200':
HTTP_CODE_200_SUM = HTTP_CODE_200_SUM + 1
print (HTTP_CODE_200_SUM)
HTTP_CODE_PERCENTAL = (HTTP_CODE_200_SUM / 100) * 100
if HTTP_CODE_PERCENTAL < 98:
print ("HTTP_CODE_PERCENTAL < 98 NOT ACCEPTABLE AND EXIT")
os.exit(98)
else:
HTTP_CODE_PERCENTAL_RESULT = str(HTTP_CODE_PERCENTAL) + '%'
print (HTTP_CODE_PERCENTAL_RESULT)
print (hostname)
if hostname == "server04":
cmd_git_commit="cd /var/lib/jenkins/workspace/GIT_CODING_TRIGGER/php/ ; sudo git checkout master ; sudo git pull ; sudo git merge stage ; sudo git push origin master ; sudo git checkout stage"
git_re=os.popen(cmd_git_commit).readlines()
print (hostname)
print (cmd_git_commit)
else:
print (hostname)
print ('hostname NOT == server04')
hostname=os.getenv('RUN_SERVER_TO_QA')
remote_check(hostname)
remote_check_php(hostname)
remote_check_web(hostname)
remote_check_curl_100(hostname,8080)
```
这一段脚本,我们大致讲解一下
主要是使用os.popen的方式,把命令行直接送到linux上执行
然后,在测试的部分,我们主要依据的是,多少次的访问请求中,多少可以正常返回
然后,计算一个最终比例。用这个,来代表测试结果
# \[白盒测试和持续集成\]
![](assets/1593412617-5d56756b7cee08dc1e4402f4f1a6616f.png)
上面这张图 给大家把 这一个单元的任务 做出了一个规划
之前已经完成的provion\_instance job , 现在 我们需要 一个测试的JOB 调用PYTHON3 去检测之前 是否成功
如上这段脚本 请先注意 最后 红色的这一句, 获取环境变量 RUN\_SERVER\_TO\_QA(很重要 Python如何在Jenkins中获取 环境变量)
![](assets/1593412617-99a116772d76a9fb30781b8efbec77db.png)
这个RUN\_SERVER\_TO\_QA 其实就是取自上游任务PROVISION\_INSTANCE的参数设置, 安装的是哪个机器,就把这个机器名当参数 传给这个PYTHON\_QATest JOB
这个任务 先检查远程机器在provision安装后 几个服务进程端口 有没有起来
然后 再进一步检查 PHPINFO 是否可以正常工作
最后检查 部署的开源站点 是否工作正常
另外 脚本的最后 还有一个函数 remote\_check\_curl\_100
这个的作用 循环从本地发起 向目标主机100词 curl请求, 如果返回100次请求中的200返回码百分比 低于98% 则测试返回失败
上游的provision 添加如下的配置 在BUILD中 再增加一项 选择 如下
![](assets/1593412617-8dcc6b7a1f7b8e287f860e79b908cfb1.png)
这个添加的触发下游任务, 跟以前有点不一样了, 首先是在BUILD中添加 不再是POSTBUILD
另外, 注意 上面的配置内容, 勾选 BLOCK UNTIL ....., 意思是说, 当前的provision任务 运行了脚本安装后, 并不立刻结束, 需要等待下游的QA\_TEST任务运行完 且没有问题的情况下, 才结束
如果下游的Python\_QA\_TEST没有通过, 则这个任务 也失败
这种设置 下游的方法 在企业中很实用, 作为一种 利用下游任务 检查上游的方法
然后 Python\_QATest JOB 也需要接受这个参数
![](assets/1593412617-bf8e72c26fd5fa21303938f50d0885e8.png)
到这里, 我们的Provision\_JOB + Python\_QA 已经完善了 一个新LNMP实例的任务
光有LNMP还不行, 我们还得把开源站点的部署加入持续集成 , 这里也是模拟 企业发布上线的流程
接下来 我们再新建三个Jenkins JOBs , 分别叫做,GIT\_CODING\_TRIGGER, Deploy\_stage 和 deploy\_prod
从名字 我们也可以看得出来, 一个任务是用作发布测试环境, 一个任务是发布生产(也就是真实上线了)
咱们之前 在GIT的那个单元的时候, 跟大家介绍过,企业中的持续集成发布 是个什么样子
如下图所示 我们再来回顾一下哦
![](assets/1593412617-3d439b32eb90505911264b6fb81fd6c2.png)
这个持续集成的流程,在真实企业中是非常复杂 且工作量巨大(图中的任何一个部分 都隐含着庞大的细节末枝)
咱们教学中 只能简单的模拟一下 这个流程,让大家有一个基本的概念,之后在工作中 就可以提前有一个很好的认知了
+256
View File
@@ -0,0 +1,256 @@
## 17.Nginx实现上传的方法
\[upload模块实现文件上传\]
nginx实现文件上传,需要依赖一个额外的安装模块upload. 跟以往一样,默认情况下,nginx不带这个模块,所以,我们需要做两件事,1:下载该模块 2:重新编译nginx
wget [https://github.com/fdintino/nginx-upload-module/archive/2.3.0.tar.gz](https://github.com/fdintino/nginx-upload-module/archive/2.3.0.tar.gz)
# 解压
```javascript
tar zxvf 2.3.0.tar.gz
cd nginx-1.12.2
./configure --add-module=../nginx-upload-module-2.3.0/ --prefix=/usr/local/nginx
make
make install
```
# 启动nginx
`/usr/local/nginx/sbin/nginx`
# 修改nginx的配置文件,添加如下这一段
```plain
## -------> 新增内容 <-------------------
location /upload {
upload_pass /res_upload;
upload_store /usr/local/nginx/html;
}
location /res_upload {
default_type text/html;
return 200 "success";
}
## <------ 新增内容结束 ------------------->
```
# 重启nginx,让配置生效
然后在 /usr/local/nginx/html目录下新增一个 file.html(名字别用 upload.html 不然会命中 /upload 这个location的规则,踩坑才跳出来),用于浏览器上传文件使用:
```xml
<html>
<head>
<title>Test upload</title>
</head>
<body>
<h2>Select files to upload</h2>
<form name="upload" method="POST" enctype="multipart/form-data" action="/upload">
<input type="file" name="file"><br>
<button type="submit"> Upload </button>
</form>
</body>
</html>
```
然后再浏览器打开这个页面,就可以上传文件了
\[PHP实现文件上传\]
我们之前也尝试过编写PHP,所以这里就不再赘述了
我们准备好一个HTML页面,然后再准备好一个PHP的源代码文件
```xml
<!-- 这是提交给nginx -->
<html lang="CN">
<head>
<meta charset="UTF-8">
<title>Test upload</title>
</head>
<body>
<h2>Select files to upload</h2>
<form enctype="multipart/form-data" action="/upload" method="post">
<input type="file" name="file"><br><br> <input type="file" name="file1"><br><br> <input type="file" name="file2"><br>
<input type="submit" name="submit" value="Upload">
<input type="hidden" name="test" value="value">
</form>
</body>
</html>
<!-- 这是直接提交给php -->
<html lang="CN">
<head>
<meta charset="UTF-8">
<title>Test upload</title>
</head>
<body>
<h2>Select files to upload</h2>
<form enctype="multipart/form-data" action="/upload.php" method="post">
<input type="file" name="file"><br>
<input type="submit" name="submit" value="Upload">
<input type="hidden" name="test" value="value">
</form>
</body>
</html>
```
1:通过配置Ningx 安装 Upload Modules 进行 文件上传 再从PHP 接受 Ningx POST过来的参数。
2.直接通过编写PHP,从HTML 负责文件上传
```plain
server {
listen 80;
server_name localhost;
charset utf-8;
access_log /usr/local/nginx/logs/access.log main;
client_max_body_size 100m;
location /upload {
upload_pass /upload.php;
upload_store /usr/share/nginx/html/file 1;
upload_store_access user:r;
upload_set_form_field "${upload_field_name}_name" $upload_file_name;
upload_set_form_field "${upload_field_name}_content_type" $upload_content_type;
upload_set_form_field "${upload_field_name}_path" $upload_tmp_path;
upload_aggregate_form_field "${upload_field_name}_md5" $upload_file_md5;
upload_aggregate_form_field "${upload_field_name}_size" $upload_file_size;
upload_pass_form_field "^submit$|^description$";
}
```
# 如果是当前端口,设置proxy\_pass会出现错误
```php
#location @test {
# proxy_pass htpp://127.0.0.1;
#}
location / {
root /usr/share/nginx/html/work/public;
index index.html index.htm index.php;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
location ~ \.php$ {
root /usr/share/nginx/html/work/public;
fastcgi_pass unix:/opt/remi/php70/root/run/lock/php-fcgi.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 15d;
}
location ~ .*\.(js|css)?$
{
expires 1d;
}
}
<?php
header('Content-Type: text/html;charset=UTF-8');
$type = ['.pdf' => 'application/pdf'];
const FILE_DIR = '/usr/share/nginx/html/work/';
if (count(($file = $_FILES['file'])) > 0) {
if ($file['error'] == 0) {
if ($fileType = array_search($file['type'], $type)) {
//以当前的时间命名目录
$date_dir = date('Y-m-d', time());
if (!is_dir(FILE_DIR . $date_dir)) {
if (!mkdir(FILE_DIR . $date_dir)) {
return header('location:503.html');
}
}
$filename = FILE_DIR . $date_dir . '/' . (md5_file($file['tmp_name']) . time() . rand(9999, 99999)) . $fileType;
//生成新的文件
if (rename($file['tmp_name'], $filename)) {
return header('Location: success.html');
}
}
}
switch ($file['error']) {
case 1:
http_response_code(400);
exit('文件大小超出了服务器的空间大小');
case 2:
http_response_code(400);
exit('要上传的文件大小超出浏览器限制');
case 3:
http_response_code(400);
exit('文件仅部分被上传');
case 4:
http_response_code(404);
exit('没有找到要上传的文件');
case 5:
http_response_code(503);
exit('服务器临时文件夹丢失');
case 6:
http_response_code(503);
exit('文件写入到临时文件夹出错');
}
}
```
# 如果是nginx upload module
```perl
if (count(($file = $_POST)) > 0) {
if ($fileType = array_search($file['file_content_type'], $type)) {
//以当前的时间命名目录
$date_dir = date('Y-m-d', time());
//如果目录没创建,我们就自己创建一个
if (!is_dir(FILE_DIR . $date_dir)) {
if (!mkdir(FILE_DIR . $date_dir)) {
return header('location:503.html');
}
}
$filename = FILE_DIR . $date_dir . '/' . (md5_file($file['file_path']) . time() . rand(9999, 99999)) . $fileType;
//生成新的文件
if (rename($file['file_path'], $filename)) {
return header('Location: success.html');
}
}
}
```
@@ -0,0 +1,78 @@
## 18.Nginx服务器安全相关(上篇)
保证nginx安全,其实很大程度上就是保证服务器的安全
所以,我们需要使用防火墙来保证服务器上没有问题流量
(1) 防火墙的概念是什么
防火墙这个词 我们一点也不陌生 从早年大家都在玩 windows9x的时候 防火墙就一直伴随着咱们一路走过来了
对于普通用户 接触最多的 无非就是 win上面 形形色色的 各种防火墙软件了 (有相当长一段时间 防火墙软件产品层出不穷)
那么到底什么是防火墙呢? 区区只认识到"防火墙软件” 这么一个层面 够吗?(对咱们Linux架构师来说 自然是远远不够的)
防火墙说白了 其实是一种隔离技术, 就是把 从外面进来的访问(进入操作系统 或 进入下一个网络设备)按照事先定义好的一套规则
进行过滤 合格的访问允许它通过,不合格的就直接丢弃了
防火墙是在两个网络通讯时执行的一种访问控制尺度,它能允许你“同意”的人和数据进入你的网络,同时将你“不同意”的人和数据拒之门外,最大限度地阻止网络中的\*\*\*来访问你的网络。换句话说,如果不通过防火墙,公司内部的人就无法访问Internet,Internet上的人也无法和公司内部的人进行通信。
防火墙有软件的,也有硬件的. 有工作在应用层上的(比如win防火墙),也有工作在底层的(比如LINUX防火墙 iptabls)
对于硬件防火墙 主要指得是 各种网络安全硬件设备 (这个不在我们这堂课的讨论范围中)
我们主要是 针对工作在操作系统层面的防火墙进行学习(Linux iptables)
(2) iptables是什么?做什么用?
Linux中 最普及的安全防火墙 就是 iptables
iptables其实是经过几个阶段发展而来 , ipfirewall -> ipchains -> iptables (关于这个发展历程 由于太古老了 我们也没有太大必要了解的很详细)
首先我们需要知道的是
iptables 在Linux系统中身份比较特殊
第一:表面上 iptables是一个命令 也是一个服务
如下图所示,iptables本身就可以直接运行 查看使用手册
![](assets/1593412661-cd5bbb9aa02bcd82739d41586b59b4f6.png)
![](assets/1593412661-66076d7fed9dee353321a26a6d01d652.png)
为什么会出现这样的现象呢?
因为在linux上,iptables 防火墙其实是系统内核的一部分,真正干活的实际上是内核当中一个叫做Netfilter的功能框架(对于内核Netfilter深入知识感兴趣的朋友 可以自行扩展学习)
也就是说 Linux内核利用其自身对于网络数据包的流向判断 来间接起到防火墙的功能
而iptables呢? 其实只是作为 方便我们用户 随时控制内核中netfilter功能的一个平台或者是桥梁 (ipfirewall -> ipchains -> iptables)
总结来说: 真正的防火墙是 Linux的内核提供的, iptables可以理解为 使用这个防火墙的一个"bash" (Linux内核防火墙 - 专有命令行)
说到这里 我们也就回答了上面的提出的那个疑问了,因为防火墙其实是在内核中,并不是简单的一个软件跑在系统上,那么自然 ps top命令就看不到这么一个 iptables进程了
顺带着,我们也就明白了 linux防火墙到底是怎么回事了,不过为了方便理解和使用习惯 咱们依然还是称作Linux防火墙是 iptables
下面附带了一张图 方便我们理解 Linux防火墙是怎么回事
![](assets/1593412661-2e05ca80e15a41b765e5325dd9c3a5d2.png)
3) iptables最容易理解的第一步: 过滤型防火墙(filter表)
光说不练是不行的, 我们接下来 就直接接触一下 iptables 最容易理解的一个防火墙类型 -> filter过滤型的防火墙
(我们这里先暂时不管 iptables的什么4个表 5个链 )
咱们先说说什么是 过滤型的防火墙吧
最直接的例子 就是咱们平时玩windows安装的各种防火墙软件了
windows各类防火墙 其实最基本的一个作用就是过滤
比如: 当我使用浏览器要下载一个软件时,防火墙会给我弹出警告(xxxx软件 可能会伤害你的操作系统 )等等之类的提示语言
要运行一个软件时 会弹出一个窗口 告诉你 xxx软件可能含有病毒(虽然很多时候 都是误报….)
![](assets/1593412661-4616bba069103dd4a47c928723bb89bb.png)
其实这就是一个典型的过滤形式的防火墙
过滤:指的就是 当外界的东西 新来的东西 想进入我当前的操作系统时,或者是 本地希望向外访问未知区域 并获取资源时
在这中间卡了一道关卡 用来检查其安全性 出现疑似问题时 给予警告和处理
这就是windows的过滤型的防火墙
然而Linux的iptables防火墙 也一样具备这种过滤的功能
虽然基本原理相通 不过跟Windows防火墙比较起来,有很多的不同 接下来我们就看看有什么不同点
1) Linux防火墙工作在内核 在操作系统更往下的底层 , 而win防火墙(大多数)是工作在应用层 也就是说 是一个软件起到防火墙的作用
2) Linux防火墙的安全防护更加彻底,且非常固执且严谨 一板一眼 说一不二!
而win防火墙 很多时候就有点老好人 模棱两可的意思 (应用层的防火墙 考虑的因素太多 很难100%判断是否有害)
3) Linux由于绝大多数是 以一个服务器提供对外服务的身份工作, 所以防火墙更多的是 用来过滤外来的请求 基于网络层面的请求
而windows更多的是 个人电脑使用,不会对外提供服务(win server),所以 防火墙更多是判断出去的请求 或者本地即将执行的程序的可靠度 (iptables是做不到这一点的 不过其实也不需要)
@@ -0,0 +1,93 @@
## 19.Nginx服务器安全相关(中篇)
接下来 我们来看两个 过滤类型的 iptables配置 简单的了解一下
有时候我们发现某个ip不停的往服务器发包,这时我们可以使用以下命令,将指定ip发来的包丢弃:
```coffeescript
[root@server15 ~]# iptables -A INPUT -s 172.16.0.0/16 -j DROP
[root@server15 ~]# iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
DROP all -- 172.16.0.0/16 anywhere
```
如上是第一个例子 ,把来源IP地址是 172.16.0.0/16 的请求丢弃掉
`root@server15 ~]# iptables -A INPUT -s 192.168.0.0/16 -j ACCEPT`
接下来的 这个例子 如果来源IP是192.168网段的 就允许通过
如上就是两个最简单的 通过iptables设置过滤类型的防火墙配置
iptables 4个表(tables)代表什么意思
通过上面一讲 我们对过滤类型的防火墙 有了一定的了解
(过滤防火墙 )
所谓过滤:就是符合某种条件就通过,不符合就丢弃
接下来 我们顺着这个 “过滤型” 继续学习
其实大米在前两节课中 反复给大家举的 过滤型 的例子 是为了方便初学iptables的朋友
过滤型的防火墙规则 在iptables当中 实际上对应的是 iptables当中的一个”表” ,也就是filter表(过滤)
iptables在使用过程中,有4种标准化预定义的 “表” (tables)
每一种表(table) 是代表了一种使用的类型 或者说方向 (比如咱们之前提的 filter表)
除了filter table之外, 其他还有三种tables
```plain
filter / nat / mangle / raw
```
其中 前两项 filter & nat 表 可以说是日常工作当中 接触频率最高的 同时 也是咱们本课程上篇的主讲内容
关于 mangle 和 raw 属于高级的扩展用法 难度较大 且实际工作中出场率不算高 所以我们放到 下篇去讲解
filter和nat表 是我们作为一个合格的Linux运维 必须必须优先掌握的 不然 你也别说自己会iptables了 - \_ - …..
filter table 主要是起到 过滤的作用 根据一定的规则 把即将发送到OS的请求 或者是即将发送出OS的请求(或者是转发的请求) 预先做一个判断
根据判断结果 ,决定是否放行. — 这个就是作为防火墙 提供的最基础 也是使用频率最高的用法 (windows防火墙 主要也是做这个事)
![](assets/1593412681-0270e42657acec0bdf75871abf591b9c.png)
nat table 的作用跟filter就大不一样了 (以往 对于防火墙的认知)
NAT (Network Address Translation,网络地址转换)是一种对数据包的特殊处理
其主要的目的 是通过iptables防火墙 把原本发送过来的请求 更改其原始的目的地址 或者 更改其来源地址 (port 端口)
以做到 将请求转发的目的
这么说 可能不好理解 ,咱们来看下下面这张示意图
![](assets/1593412681-0f2ce1d4cfc4d49aac57b7939af4b774.png)
在上面 这张图中 从最左边 原本一个请求 是发送到 IP地址A的服务器, 但是经过iptables-nat规则之后
把请求的目的地址 从IP:A —>改成了 IP:B
于是 请求就这样被转发到 IP:B上去了
除了IP地址的改变之外 ,我们再来看一个 改变端口的例子
这里只有一台服务器, 原本请求是发送到这台服务器的80端口
通过NAT表之后,请求的目的端口被改变到 8080了 ,所以请求被送往8080端口所属的应用
通过如上两个例子 我们也就可以 大致明白了,iptable NAT表 不是对请求进行过滤,而是把请求的IP地址或者端口进行临时的转换
以达到转发的目的 这种使用方法 在实际工作中也是很常见的,很多架构中的应用 其实也是基于这种原理实现 比如:反向代理,负载均衡(LVS NAT) 等等
```plain
iptables 使用中 -t 参数 代表的就是 表
iptables -t filter -L : 显示 filter表中 都有什么规则
(filter是默认表 ,不加 -t filter 也是一样的)
iptables -t nat -L : 显示 nat表中 都有什么规则
```
如上是查询
修改和添加 也是一样的方法
```plain
iptables -A INPUT -t filter -s 172.16.0.0/16 -j DROP
iptables -t nat -A PREROUTING -p tcp -d 192.168.102.37 --dport 422 -j DNAT --to 192.168.102.37:22
```
@@ -0,0 +1,186 @@
# 【先跟着大米 手把手 跟nginx混个脸熟】
上一个篇幅中 我们补了补服务器和一些概念 做了一些Linux系统上铺垫的准备工作
接下来 终于到了我们该动手实践nginx的时候了 小兴奋?^\_^
关于各种复杂的原理 咱们暂时先扔一边去 先把nginx跑出个样子是真的!
(在整个过程中 各种Linux的操作 捎带着也复习了)
首先 咱们先去到nginx的官网看一看吧 (www.nginx.org)
(补充说明 今后无论学习还是工作 不管是哪一项技术 遇到吃不准的疑难时 都尽量以官网为准 这一点应该不需要过多解释了吧,英文不过关?没事 有大米呢 怕个球啊 ^\_^)
nginx的首页比较务实 没那么多花俏 首页显示的是nginx news (最新动态)
![](assets/1593412186-0529e62a038deca9f98696808e999b46.png)
随意点击一个 nginx-1.xx.x 就可以来到 nginx的官方下载页面了
下载nginx是咱们的首要任务 ,不过先别猴急 大米带大家先 认识一下 官网发型的几个不同的版本标识是啥意思
![](assets/1593412186-ac4712ba01ae995e6e4466c6bc792b92.png)
从上面的截图我们可以看到 Nginx官网提供了三个类型的版本
Mainline version:Mainline 是 Nginx 目前主力在做的版本,可以说是开发版
Stable version:最新稳定版,生产环境上建议使用的版本
Legacy versions:遗留的老版本的稳定版
在这里 大米只推荐大家 下载和使用 最新的stable版本 当前也就是1.14.1
(稳定版 一般是经过完善接口功能测试的版本 理论上是符合运行在咱们生产环境上的版本)
提问: 啥叫生产环境? - -
回答: 在你上班的企业 用来服务你们的用户所搭建的服务器 就叫生产环境 (也叫线上环境) 是核心最重要的环境了 (自然用稳定版 毋庸置疑)
接下来 说干就干 咱们在网页上复制下载链接 然后直接在咱们自己的其中一台Linux上 执行下载
![](assets/1593412186-3d2f472dbda519ea6c3af5a86add7899.png)
提问: 为啥我的Linux上 找不到wget啊啊!
回答:yum install wget 就有了啦
下载完成后 是一个tar.gz结尾的压缩文件, 我们在Linux上执行解压缩
![](assets/1593412186-7ad9d351b5719c2db0b2f715b23da1cd.png)
解压成功后,就生成了 这样的目录
提问: 为啥我下载的 nginx.tar.gz 解压缩报错啊
回答: 如下图所示 请你确认你下载的文件 是否正确 使用file 命令确认文件类型, 用md5sum命令确认你下载的完整性
(md5sum 可确认文件的完整性, 生成的加密字串 只有在文件完完全全一模一样的情况下 才能保证一致哦)
![](assets/1593412186-15a6a2506e22c984668cc3d4e211857c.png)
没有问题后,接下来该做什么呢? 直接就可以运行启动nginx??
NO NO !~, 我们当前只是下载并解压了nginx 1.14.1 的"源代码" ,都还没有生成可执行文件呢 怎么能运行的了呢
(Linux下面的 可执行文件 就是指的直接 ./software\_name 就可以运行跑起来的 )
提问:nginx的源代码在哪里? 我要是看不懂怎么用它啊
回答:nginx的源代码 就在你刚刚下载并解压之后的 src/目录下哦 请看下面的图
![](assets/1593412186-77377d9b73123fb8e25958e6af2473a7.png)
从上面的截图 我们可以看出 src目录就是存放源代码的 其下面按照 nginx不同的组成模块 划分出 core/event/http....等等
提问:为什么nginx/src下面 会看到多个子目录
回答:因为Nginx是一个高度模块化的WEB 服务器, 所谓的模块化 就是软件在开发设计的时候 就按照不同层次 不同功能 将代码分开封装 你所看到这几个自带的目录 是官方默认提供的模块 当有请求到达的时候,请求依次经过这条链上的部分或者全部模块,进行处理。 我们后面 还会有专门讲nginx模块的篇章 这里只要先知道就好
看到了这些源代码 但是我们现在又看不懂里面的代码怎么办?
这个不用着急,因为目前还不需要我们去读懂它的代码 我们只需要掌握 如何把这种源代码形式的NGINX 转换成可以使用的NGINX即可
这就是所谓的 开源软件-源代码安装流程
(大多数Linux下的开源软件 都采用很类似的源代码安装方式 所谓一通百通)
安装的流程在NGINX这款软件下 非常的简单 三步即可
./configurate
make
make install
完事了。。。。 接下来 我们赶快运行一下 试试看
![](assets/1593412186-d99dfe7f73cc1044dc8d1e7946d1e9a9.png)
接下来 运行
make && make install
安装完毕 ! 就这么容易
提问: 安装运行的这三个步骤都是干嘛用的啊~~
回答:configurate 命令是一个由作者手动编写的 bash脚本 (不知道Linux下的bash? 那只能自己多补补课了)这个脚本做了很多 "幕后"的工作 包括检测你的操作系统版本 已经安装的软件是否合适 最终生成MakeFile文件
make呢? 就是根据上一步生成的MakeFile文件 执行 "编译工作" , 所以说 这里才是真正的 把源代码开始进行转换工作 ,最终生成了 我们可以直接使用的二进制文件(可执行文件)
make install 呢? 这个做的是最后收收尾的工作 例如 最后把nginx的配置文件 和 二进制文件 都放到预先指定好的目录中去
接下来 我们做个小实验 看下configurate都做些什么检查
比如 还记不记得 我们上一篇中最后 让大家准备Linux环境的时候 让通过yum安装 pcre & pcre-devel 对不?
我现在把 pcre-devel卸载掉 然后再运行一下 configurate 试试
![](assets/1593412186-776ebffc57fd96b619cd39bc166b5741.png)
立刻就检查出了问题, 系统中缺少了 pcre的相关开发库 这就是configurate检测出来的
而最终当我们 执行了 make install成功后, 我们就可以进入 如下目录来看下咱们的NGINX了
![](assets/1593412186-29b9fd9990de4414810e75664dbdd7d4.png)
如上图所示,我们默认的那三个步骤,就会最终把nginx安装到 这个路径中 (/usr/local/nginx/)
接下来 咱们就找找nginx的可执行文件在哪里吧
这个就是了 ,我们接下来就可以通过它 对nginx下达各种命令啦~~~~
别的先不管 先运行起来 nginx
![](assets/1593412186-0b0656df6281f535e6c2fa9bb1880c16.png)
这样 我们就可以 把nginx启动起来了 , 并且 我们使用netstat来查看 nginx进程是否已经跑起来在80端口了
(sbin/nginx 就是最终生成的 nginx可执行文件 )
(我们之前说过了 nginx是一个处理http协议的web 服务器, 而http协议的一个非常重要的特点 就是默认工作在80端口)
现在nginx是跑起来了 , 但是我们还也没有去编写任何网页 ,怎么才能看到效果呢?
其实nginx本身就提供了默认的欢迎页面 我们直接就可以通过浏览器 或者命令行来尝试访问了
按照如下的方法
![](assets/1593412186-2a23f08248b7796d861068c4bfb6b0f4.png)
如上两种方法 都可以用来测试
第一种方法: 使用浏览器访问你的NGINX主页
你可以直接输入你的虚拟机ip地址即可 , 比如 [http://192.168.0.5/](http://192.168.0.5/)
我的截图中 输入的是 [http://server01](http://server01/) , 这是因为 我实在 自己的本机上捆绑了主机名 /etc/hosts server01 ipaddress
第二种方法: 使用curl命令行来访问
这种访问方式 是在命令行下 模拟浏览器访问的效果 (换句话说 就是命令行 发送http请求)
Nginx默认提供的欢迎主页出来了 没问题
不过 这毕竟只是官方给出的欢迎页面啊 不是说好了 咱们弄个属于咱们自己的首页么?
接下来 就跟大米来一起做一下
首先 我们得先找到之前看到的官方页面在哪里 对不?
咱们一起打开nginx的默认配置文件nginx.conf(也是咱们第一次接触配置文件了)
这个文件的位置就在 你的nginx的安装路径下 入下图所示
![](assets/1593412186-91de69609fa89f6ab41f88497c0d1322.png)
打开这个文件后 咱们找到如下的字段
注意root这一行了么
这里代表的就是 网站的根在html的相对目录下(包括咱们刚才看到的 网站的首页)
那么这个html目录又在哪里呢?
其实就在如下的位置
![](assets/1593412186-983602b3afc9b814d48a65ee32f7a0b5.png)
/usr/local/nginx 是咱们的安装目录, 那么如果配置中写成 root html 指的就是 在安装目录下 有一个相对目录叫html , 这个html目录 就是网站的根 换句话说 当访问网站的时候 所有静态资源 从这个目录里面调取
那么讲到这里,大家应该也看出来, 在这个目录下 有个 index.html
其实这个index.html就是 我们刚才看到的nginx默认给出的欢迎页面了
不信 自己可以 vim一下 index.html文件当中的内容
![](assets/1593412186-09badc7bff71323b10bb6bf459edf2fd.png)
是一个使用HTML的简单页面 只有几句话而已
接下来 咱们可以尝试 在这个index.html中随意做些改动
让它变成一个 咱们自己的首页。
@@ -0,0 +1,373 @@
## 20.Nginx服务器安全相关(下篇)
进入下篇之后 我们继续沿着这个框架和思路推进
把剩下的两个链 和 2个表学习过关之后 (raw & mangle 表 学习难度较大),我们需要再补充iptables的几个进阶的重要知识点(也顺带扩展一些Linux的网络知识 )
接下来 我们会把 一整套数据包在整个iptables(内核netfilter)中的经过流程 给大家做进一步的清晰化
之后我们开始 iptables结合企业集群的实际情况 学习合理的设置方法 以达到最有价值的效果
(1) POSTROUTING链的学习 -> 数据包<路由后><出站前> (SNAT功能的实现)
我们上篇最后 学习了 iptables数据包传送的 下路路线
走下路的数据包 其实就是走NAT(数据包地址转换)的路线
我们这里再复习总结一下吧
PREROUTING最终判定为下路之后, 数据包经过的路径是 (不考虑数据包中途被丢弃 和其他更高阶的扩展表的情况下 )
\-> PREROUTING -> FORWARD -> POSTROUTING
PREROUTING链的作用:决定目标地址的改变与否 + 上路下路最终判定 (是过滤型防火墙 还是NAT型防火墙 )
FORWARD链的作用:是否允许被防火墙继续转发 + 是否允许使用Linux的路由功能/转发功能
那么POSTROUTING链呢?
我们先给出 POSTROUTING链 的定义和作用吧:
POSTROUING链 : 是内核(防火墙功能)对数据包在经过路由表之后 , 加以处理的最后一个链,其作用是决定数据包是否需要 改变<源地址>
介绍到这里以后 我们不难发现 PREROUTING和POSTROUTING 是一对儿, 一个改变目标地址 一个改变源地址
重要知识点:PREROUTING和POSTROUTING 这两个链 一个负责目标地址 一个负责源地址 这个是iptables定死的规则
不能交叉互换 (比如你想用 POSTROUTING改变目标地址 这是不可能的)
这里有一个问题: 为什么PREROUTING只能改变目标地址, 而POSTROUTING只能改变源地址?
下面会给大家解答
(2)结合内核路由 分析 PRE / POST - routing 链互斥互依的存在含义
在上面学习了 POSTROUTING链之后 ,我们明白了其基本的作用 就是修改数据包的源地址 (在离开操作系统出去之前)
那么上节课最后给大家留下了一个问题 ( 为什么PREROUTING只能改变目标地址, 而POSTROUTING只能改变源地址?)
这种限制 难道仅仅是因为 iptables起的一个规矩而已嘛?
回答自然是否定的
为了讲清楚这个问题 我们需要扩展一下Linux网络传输的知识
我们之前反复提过的 上路 下路 以及链于链的衔接 跳转 等等...
这些其实 都为了说明两方面的含义
第一: 表明了iptables防火墙的 链与链之间的关系 先后顺序
第二: 表明了数据包在内核中 在Linux中 是如何走向的
我们可以把 1 2 3 4 5 6 的连接线(数据包走势)分成如下三个基本类型 以方便大家的理解
<1> 从外面进入到本机的数据包 (1) -> (2)
<2>. 不进入本机的 且需要过路由的数据包 (或者说需要用到地址转换的数据包 ). (1) -> (3) -> (4) -> (6)
<3> 由本机由用户 主动发起出去 离开本机的数据包 (5) - (6)
这三个基本类型的数据包流向 还是比较容易理解的
PRE-ROUTING 和 POST-ROUTING 这一对儿链对于数据包修改的不同 其实就是围绕着路由表的概念 而引起
为了 能继续深入的学习这个部分 我们先插入下一小节的扩展内容 (搞清Linux和路由相关的几个概念)然后再推进
(3)路由/路由表/转发 概念搞清楚 以及和iptables的联系
在继续往下推进学习之前 我们有几个概念 需要先给大家捋清楚 (很重要的几个网络相关的概念 不可以混淆了 不然越学越蒙圈)
第一个要搞清概念: 路由表
比较官方的定义:
路由表是指路由器或者其他互联网网络设备上存储的一张路由信息表,该表中存有到达特定网络终端的路径,在某些情况下,还有一些与这些路径相关的度量。
比较大白话的定义:
路由表在Linux中 首先是一张 可见的 可更改的表, 它的作用 就是当数据包发到Linux的时候 , 系统(或者说内核)就根据这张表中定义好的信息 来决定这个数据包接下来该怎么走
重要知识点:在Linux当中 只要有网络传输的发生 就一定会跟路由表有最直接的联系,包括上面跟大家说的 三种基本数据包流向图 一样都要经过路由表 来决定这个数据包下一步该怎么走
第二个要搞清楚的概念:路由功能 和 路由器
有了路由表之后,Linux就可以遵循这个表中的路由信息 决定数据包的去向了
不过 数据包最终在跨越网段的时候 (发往其他的网络) A B 这个时候 必须借助网关设备才可以到达其他网络
网关设备:是用于连接两个不同网络的媒介, 可以是一个硬件的路由器 (我们平时家里用的那种 小路由器 就是这个功能)也可以是一个开启了 路由功能的 Linux主机
一个开启了路由功能的Linux 就可以作为一个 网关设备使用 (实际上 大米之前所在的两家公司 内部员工上网 就是使用Linux服务器做的 而并不是买的 独立路由设备)
第三个要搞清楚的概念:转发(数据包转发)
数据包转发 是一种网络上对一个数据包进行的特殊处理 可以理解为 是在数据包传输过程中的一种中转 或者说衔接
Linux本身就提供了 数据包转发功能
如何开启?
使能数据转发功能:
echo 1 > /proc/sys/net/ipv4/ip\_forward
禁止数据转发功能:
echo 0 > /proc/sys/net/ipv4/ip\_forward
这个地球Linux人都知道~~~
不过 如下的知识点 你可能不知道
重要知识点: Linux开启了数据包转发功能后,其实起到了两种功效
1) Linux本身 可以转发数据包了 自身就形成了一个 小路由器了 可以用来当网关使用
(既可以让全公司员工实现上互联网, 还可以作为 公司内部不同局域网之间的网关)
2)Linux开启了 数据包抓发功能后, iptables NAT才可以正常使用
也就是说 NAT这种 针对网络地址转换的功能 必须建立在 Linux开启数据包转发功能的基础上
(后面 讲DNAT SNAT的时候 我们会用具体实例 来证明这一点)
综上所述:Linux数据包转发的开启,最少开启了两种功能 : 路由器网关 + 数据包修改(NAT)
有了这几个知识点的巩固之后 我们再回到刚才上一段没有回答大家的问题
PREROUTING 和 POSTROUING 为什么一个只能修改目标 一个只能修改来源
这就是因为 PREROUTING链 是在 数据包过路由表之前 发生的(目标地址的修改)
假如一个数据包 需要被 <转发> 到一个地址去 ,那么一定会先经过路由表的查询之后 才能被转发
比如 : 我们通过iptables实现了一个 NAT的功能 把来自 192.168.56.106的请求,不让它进入本机(56.102) 而是把它转发给 192.168.56.104
如果数据包 在进入路由表查询之前 没有被修改 目标地址 (从102 =》104),那么进入路由表后 就来不及了,因为路由表就已经最终决定了这个包的下一步的去向了
所以说,PREROUTING 实现的修改目标地址 必须在 数据包进入 路由表之前发生, 也就证明了 目标地址的修改 绝不可能放在 路由表之后的 POSTROUTING链再修改 那就没有任何作用了
那么接下来 我们来说一下 为什么 来源地址的修改 只能放在 POSTROUTING才行
源地址的修改 一般情况下 只是作为将 原本的来源地址 修改为本机的IP地址 以此来实现SNAT (后面马上就会讲到)
这种情况下 不可能在过路由之前 就修改为本机一样的IP地址 A-A 这样数据包就没有办法 被Linux判断了
而POSTROUTING是在数据包离开路由表后的行为, 即将要离开本机之前 ,修改源地址,以便数据包可以传回给自己本机(后面)
这样以来 我们就明白了 PREROUTING 和 POSTROUTING的本质了
(4)DNAT 和 SNAT 技术学习
有了前面坚实的基础之后 我们终于可以开始向具体防火墙应用方面推进了
首先我们提出 两种NAT的模型
DNAT & SNAT
我们先来看一下定义吧
DNAT : Destination Network Address Translation目的地址转换
SNAT:Source Network Address Translation 来源地址转换
通过之前篇幅的学习 对于这两个概念的理解就很轻松
iptables 实现两种NAT 就是通过 PREROUTING链 和 POSTROUTING链来实现的
重要的知识点:这里需要提醒初学者一句 NAT技术 是一种网络通用技术 或者也可以说 一种技术标准
并不是iptables 或 Linux内核 所特有的技术. 其他的很多途径都会以各种方式 提供或依赖这种NAT技术. 比如 各种网络硬件设备, 各类负载均衡设备或软件 等等..
接下来 我们来看一下 DNAT & SNAT的企业实际用途
技术学了不是摆设 在工作中用起来 才是正途
iptables DNAT 在企业中用途主要为如下几种 :
(1) 实现内网站点或其他服务的对外发布
解释:公司将内部服务器的某个站点 使用iptables DNAT对外发布 可以实现员工远程连接企业内部资源
比如各种 企业内部的开发人员 DEVOP说 工具站点 监控系统 邮件系统 OA系统 等等. 不过这种实现的网站对外发布 虽然简单方便 但是
会有一定的安全隐患(如果想做足了安全系数 需要在iptables的基础上 做大量的额外工作)
所以说 这种方式的网站发布 目前在企业中 正逐渐被各种其他的高级技术所取代 , 例如 \*\*\*技术(拨号\*\*\*,\*\*\*隧道) , 验证+反向代理 等等
不过 对于那些没有太多安全限制的内部服务的对外发布, 这种iptables DNAT依然是 速度最快的发布方法
(2) 堡垒机(跳板机)的实现 增强安全系数
解释: 堡垒机或者跳板机 是作为集群登陆安全验证的一种 统一化运维管理机制
强制员工先登陆跳板机 进而再登陆后端其他机器
结合iptables DNAT之后 可以通过编写简单的登录脚本 实现快速后端间接登录
由于用户被限制在 跳板机入口登录 所以这种方法即可以实现用户行为追踪 又可以很好的起到保护后端登录安全的目的
(3)服务器本机端口转换
解释:软件服务的端口 都是可以自行修改的 这种借助iptables DNAT改变端口的情况
适用用于 某些情况下 例如 公有云或ISP 只开放特定对外端口的情况下 可以快速实现端口转发
另外 这种端口转发 也会起到一定的安全防护作用,可以将服务上软件的默认端口先用iptables都禁止访问
然后 使用其他端口 DNAT到 这些默认端口来使用
iptables SNAT 在企业中用途主要为 : 转换原地址为公网出口地址 实现企业上网
解释: 如上图的SNAT实现上网的流程为, 员工自己的电脑都只能设置内网IP(192.168...) ,通过IPTABLES的数据包转发功能 (两块网卡 一个连接内网 一个连接公网)
可以将上网的请求 发送到互联网上去(公网),不过 内网IP是无法在 互联网上传送的,并且内网IP的数据包 也没有办法被互联网返回回来
所以 在经过 内核防火墙<下路> 通过路由转发后, 在最后一步的POSTROUTING链 会把数据包的源地址 改变为本机公网网卡的地址
并且在防火墙自身留下一条记录 , 数据包之后就可以成功发送到 互联网的对端了 之后再返回回来的时候,数据包回到Linux网关,再通过之前留下的记录 把数据包再转发给后端的 对应的那个内网IP
这就是完整的 SNAT的流程
重要知识: 我们绝大部分的 所谓的”上网" 都是通过类似这样的SNAT技术实现的 (除非你的电脑 单独拨号 或者直连公网IP 上网)
只要是多人通过一根公网线上网的 都是通过这样的技术实现的 家里 或者 企业中的 路由器做的就是这个事
(5)IPTABLES PRE/POST-ROUTING(D/SNAT) 命令行讲解
接下来 我们就可以对命令行进行详解了 通过如下的几个实例
我们先熟悉一下 最常用的 iptables几个参数
\-t 指定在哪个表下设置规则
\-A 指定一个链 并指定本条规则 是追加到链末尾 (对应的还有一个 -I 将规则放在最前)
\-s 限定来源地址的 符合规则
\-d 限定目标地址的 符合规则
\-j 执行动作和处理
\-p 指定协议
首先 iptables 实现NAT地址端口修改 必须用到Linux的数据包转发功能(之前咱们系讲过)
必须先开启 内核转发数据包功能
echo '1' > /proc/sys/net/ipv4/ip\_forward
(1)PRETOUING 实现目标地址全转发
iptables -t nat -A PREROUTING -d 192.168.56.102 -j DNAT --to-destination 192.168.56.104
解释: 把 数据包目标地址是 192.168.56.102的 全部转发到192.168.56.104
(这种属于全转发 )
(2)PREROUTING 限制来源和端口的 转发
iptables -t nat -A PREROUTING -s 192.168.0.0/16 -p tcp --dport 3333 -j DNAT --to-destination 192.168.56.104:22
解释: 把 来自于192.168.0.0/16网段 且 目标端口为3333的请求 转发到 192.168.56.104:22(端口)
(之前所讲跳板机的用法)
(3)SNAT 实现源地址转换
iptables -t nat -A POSTROUTING -s 172.16.0.0/16 -p tcp -j SNAT --to-source 211.199.xxx.xxx
解释:把来源地址为 172.16/16的数据包 的源地址 改成 211.199.xxx.xxx(网关上的公网地址)
(这就是通过SNAT 实现上网了)
接下来 我们来看一个 设置DNAT的完整实例 以及需要特别注意的地方
我们就用 堡垒机的实例来举例 把ssh到不同端口的请求 转发到不同的后端机器上的22端口
iptables -t nat -A PREROUTING -d 192.168.56.102 -p tcp --dport 3334 -j DNAT --to-destination 192.168.56.105:22
通过的设置,就可以把发送到3333端口的 ssh连接 转发给 192.168.56.104:22了
不过呢… 你可以试一下 这样设置之后 能不能就立刻使用了
ssh 192.168.56.102 -p 3333
\[root@server05 ~\]# ssh 192.168.56.102 -p 3333
...
结果是不通 ... 怎么回事呢??
这里画图给大家解释一下 为什么 (这里非常重要的知识)
绿色是SSH请求发起方, 数据包到达下面的iptables时候, 经过DNAT的转换,目标地址 由102 转变为 104
当前所经过的路径是 A -> B
这里需要特别强调一点 数据包是有去有回的 (很多朋友 都非常容易忽视这个问题 只考虑去 不考虑回)
之后 请求到达 56.104(右边的粉色)之后,104处理完成后 需要将请求 返回回去
这个时候 数据包的 来源地址 依然是 106 , 104 尝试将数据包返回给106 (绿色的部分)
此时 数据包返回会失败为什么呢(尽管是处在同一网段,如果是处在不同网段 那就更不可能发回去了) 因为最原始的数据包 是106(绿色)通过 A->路径发送给102的
在TCP连接建立的基础上说 , 106(绿色)从来就没有给 104(粉色)发起过TCP连接请求 ,绿色发出的TCP请求 还在和102(防火墙机器)建立着链接 等待返回数据呢
就算数据包返回回去 也会被拒收
所以说 这种数据包通过代理(防火墙)转发的形式 , 路线必须遵从 A -> B -> C -> D 这样的顺序发送
以最下方防火墙为中心点, 左右两边 都是半边各自建立最短的直接链接 有去必有回
解决的方法 :
在这里 通过再设一条 SNAT规则 把 来源地址 106(绿色) -> 转成 102(防火墙机器的地址)
这样以来 B路和C路 就形成回路了
完整的解决方案 就是如下 请牢牢记住 , 且这个还经常作为 Linux系统工程师的 面试题 (我本人 曾经被最少 3次面试中 都问过 iptables 如何实现DNAT)
iptables -t nat -A PREROUTING -d 192.168.56.102 -p tcp --dport 3333 -j DNAT --to-destination 192.168.56.104:22
iptables -t nat -A POSTROUTING -d 192.168.56.104 -j SNAT --to-source 192.168.56.102
另外 顺带一提的是 不要忘了打开 Linux内核转发功能
一旦关闭 转发功能 NAT功能就歇菜了!!(不信你可以试试关闭 这里再次验证 NAT功能需要借助内核转发)
echo '1' > /proc/sys/net/ipv4/ip\_forward
最终 我们通过IPTABLES DNAT成功的实现了 堡垒机(跳板机)的功能
(6) IPTABLES FORWARD链学习
五个链中 只剩下这个链了
接下来 咱们来看看FORWARD链具体是干嘛的
还是通过上面这张图 我们来看一下 中间的FORWARD链
FORWARD 咱们上篇介绍过了, 在咱们的课程中 它具备两个含义 咱们再来说一次
(1) 代表了 Linux内核的路由和数据包转发
(2) 代表了 IPTABLES防火墙中 FORWARD功能(FORWARD链)
关于这个FORWARD链它具体的功能 有很多朋友对它的概念 都比较模糊
大概知道是啥意思 但是形不成具体的使用模型 餐不透
那么接下来 咱们来好好捋一捋这个FORWARD链把 (顺带着 咱们也整个捋一捋五链架构)
我们先来看一下 一个数据包 到底什么情况下 才会跟这个FORWARD链发生关系 (才会用到FORWARD链)
首先 我们先把准确的定义 给大家抛出来
FORWARD链 只会跟需要用到 Linux内核转发功能的 数据包 才会发生关系
举几个实际案例来考一考大家学习的成果
(1) 同网段下 从一台机器ping 另外一台机器 会关系到FORWARD链吗?
回答:跟FORWARD链没关系, 因为同网段下 (比如都是192.168.56.0/24的IP地址)用不到路由或Linux转发 走的是直连路由(路由表)
所以 你即便在防火墙下 加上了如下语句 拒绝一个IP使用转发功能 但是依然可以ping通 (但是NAT就不通了哦!!)
iptables -A FORWARD -s 192.168.56.106 -j DROP
\[root@server05 ~\]# ping 192.168.56.102
PING 192.168.56.102 (192.168.56.102) 56(84) bytes of data.
64 bytes from 192.168.56.102: icmp\_seq=1 ttl=64 time=0.252 ms
ssh 192.168.56.102 -p 3333 #(之前NAT堡垒机的例子)
…..
(2)如果是分属两个不同网段的机器,从一台去ping另外一台机器 会用到FORWARD链吗?
机器A(网段A) -> 防火墙 FORWARD DROP/网关(路由转发) -> 机器B(网段B)
回答是:会用到
因为 中间的防火墙机器 这个是充当了 路由器(Linux iptabls 数据包的转发)使用,左右连接两个不同的网络下的机器
那么 数据包经过的时候 会依赖Linux内核的路由功能 (路由功能 即是 数据包转发功能)
所以 这个时候 如果限制了 FORWARD DROP, 那么就ping不通了哦
(3)如果一个数据包 是要经过DNAT 或者 SNAT功能 的时候,——>(数据包的转发功能)-> 会用到FORWARD链吗?
答案是:会用到 , 因为只要是 NAT功能(地址转换) 就必然会用到 Linux的数据包转发 (但是这里 就不属于 使用路由功能了哦 这个要分清楚)
Linux 开启数据包转发的时候 , 把自身用来做一个网关(路由器)连接其他的不同的网络 ,开启NAT
FORWARD -j drop
(4)如果是在防火墙本机上 往外ping一个公网地址 会用到FORWARD链功能吗?
答案是: 不会用到, 因为防火墙本机 向外发送数据包 这个等于是把一个数据包 直接的从本机发送出去,并未用到任何跟本机相关的路由功能(但是会用到路由表 这两个不是一个意思)
通过上面四个例子的问答 我们就应该很容易得出如下的结论了
重要知识点: 只要是 会用到 Linux路由功能 或者Linux数据包转发 的请求(过Linux路由器,过Linux NAT), 就必然会经过 FORWARD链的关卡
接下来 我们进入更深层的 对FORWARD链的探索
我们在命令行 输入一下 iptables 查询
上面这个图中 我们可以看到 在filter表下 有三个链, INPUT FORWARD OUTPUT
也就是说 FORWARD链 是附属于 filter(过滤型) 表下的
然而 对应 我们一直所用的 这张图来说 FORWARD链 是属于 1346 下路的一个关卡
也就是从 单单 上路下路 来看,感觉 好像 FORWARD链 应该是附属在 下路 NAT(也就是数据包修改), 跟上路没什么关系貌似
但是 通过之前的讲解 我们也证实了 即便是走上路的情况下 (不涉及数据包修改 只是数据包传输 )也可能会跟FORWARD链产生关系
比如: Linux 充当网关使用的时候。走的是上路 不是下路 (因为不需要NAT转换地址)但是 由于需要用到转发功能 所以 也会跟FORWARD有直接的关系
知识点总结 : FORWARD FILTER表, 上路 下路(一定 NAT),数据传送 数据转换
所以 我们之后 会在这个图的基础上 提出一个更完整的 IPTABLES架构图 (之前不提出这个 完整架构图 是为了教学的目的 不然一上来就看完整IPTABLES图 会直接晕菜的…
+208
View File
@@ -0,0 +1,208 @@
## 21.Nginx和lua的结合
\[先来看看什么是网络attack\]
attack的本质是什么? 其实说到底 就是 对服务器现有资源的强力打击 或者说是消耗
那么从我们运维架构的角度出发, 企业中的服务器资源的又有哪些分类呢?
第一类:服务器物理层面的 资源
这个是最好理解的,无非就是 CPU / 内存 / 硬盘 /,这些都是作为计算机物理层面上的 有限资源
第二类:OS操作系统层面的 资源
我们就以运维的核心OS Linux为基准
那么操作系统的资源 简单举几个例子 , 端口数量,连接数 , TCP列队数, 文件句柄数 , 进程调度/优先级 , 等等
网络资源
这里主要指的是网络带宽,这是非常珍贵的资源,后面也会具体的讲解
上面提到的三种类型的资源 都是作为一个集群架构的有限资源 attack的本质其实就是对资源的消耗
资源的消耗殆尽 最终会致使服务器无法再响应用户的请求,这也就是 咱们常说的 Dos 拒绝服务attack
另外,如上提到的三大类的资源 彼此并不是独立的 之间实际上都有着 大量的连带关系
现如今都是互联应用时代,一切都走网络,所以 网络资源的消耗 自然是不言而喻的
就算我们暂时忽略掉 IP包在路由途中的过程, 就算是直接到达了 我们的服务集群
在我们的集群中 也会发生一些列的 连带其他资源的消耗
如下图所示, 比如 一个HTTP的请求到达之后,按照标准七层协议的框架 由下至上 从物理层一直到应用层 都会串联起来
![](assets/1593412729-045ac48d1340832185a094a27cc711dd.png)
网卡会进行IP包重组,TCP/UDP会进行传输层的连接建立,连接的建立必定又会继续向上 消耗系统的 CPU/RAM/IO , 端口,连接数,列队数 , 文件句柄数 ,等等
任何一种资源 如果出现瓶颈 都会牵制其他的资源
从attack方来说, 逐渐由高难度的 4层attack , 逐渐转变为 傻瓜式的 7层attack
例如:后面要讲到的 基于4层的 系统漏洞attack(主要指的是TCP/IP 三层和四层协议)
这种attack不但要精通TCP/IP协议,还要掌握系统底层知识,以及代码的功底
从流量要求很小的Dos attack, 逐渐变成并发量大的Ddos attack (Distributed dental of service)
原本 在操作系统(主要指的是Linux)内核较低时,服务器性能较低时 , 少量的attack即可造成系统瘫痪
随着OS和服务器的提升, attack流量 有着越来越高的要求
从早期的 attack物理层 attack系统层,造成第一类 第二类资源的消耗,逐渐过度到 网络带宽的消耗
另外就是费用问题,攻方和守方的费用 其实都是一直在增长的
\[谈一谈现如今的Ddos\]
DoS是Denial of Service的简称,即拒绝服务,其目的是使计算机或网络无法提供正常的服务。带宽attack指以极大的通信量冲击网络,使得所有可用网络资源都被消耗殆尽,最后导致合法的用户请求就无法通过
什么是分布式拒绝服务(DDOS)
分布式拒绝服务(DDoS:Distributed Denial of Service)指借助于客户/服务器技术,将多个计算机联合起来作为平台,对一个或多个目标发动DoS attack,从而成倍地提高拒绝服务的威力。通常,攻方使用一个偷窃帐号将DDoS主控程序安装在一个计算机上,在一个设定的时间主控程序将与大量代理程序通讯,代理程序已经被安装在Internet上的许多计算机上。代理程序收到指令时就发动attack。利用客户/服务器技术,主控程序能在几秒钟内激活成百上千次代理程序的运行。
随着计算机和网络技术的发展,硬件设备的处理性能加速度增长,成本也变得非常低廉,网络的快速发展更是让带宽、出入口节点宽度等大大的提升,这让传统的DoSattack很难凑效。
 
\[补充学习一下TCP的知识\]
我给大家提出了一个 U型结构 的TCP/IP(这又是什么鬼??)
先别着急,咱们接着顺着上一节的末尾 继续往下说
进入80年代初后,计算机的发展是越来越快了,伴随着的 自然就是对网络的需求
但是 在这个发展时期 又出现了一个比较严重的问题 是什么呢?
BASIC语言 虽然古老 但是在那个年代 是一个非常重大的里程碑,它是第一个可在当时称为 高级编程语言的
(高级编程语言 指的是相对于 更原始的 汇编语言 和 后来 FORTURN语言,这种接近机器底层语言 而说的)
高级编程语言的出现,极其快速的推进了计算机单机的发展,很快就出现了所谓的 应用程序/应用程序编程
什么是应用程序呢? 简单的来说,就是通过编程的发展,把计算机的功能层层嵌套 不断的往上堆积
让人使用计算机的难度 越来越低 看上去是一件好事,但是对于网络的发展来说,确是一把双刃剑
什么意思呢? 这把双刃剑是什么 ?接下来 咱们继续说
高级语言的诞生 加快了单机的发展, 然而 协议的混乱状态,又拖延了网络的发展
造成的结果就是, 网络的发展跟不上单机的发展 (咱们就拿游戏来举个例子 , 经常玩电脑游戏的我们都知道, 单机游戏有着漫长的发展史,网络游戏的泛滥也不过是近十多年的事情,而单机游戏 在BASIC时代其实就出现了,而游戏本身 其实也是 应用程序的一种体现)
接下来 我们详细来看下 单机 和 网络出现的矛盾 到底是什么
我们所理解的 计算机 到 计算机 的网络通讯 大概是这个样子
感觉不就是点 到 点 就可以通讯了吗?(偏向宏观的认知)
其实不是的,我们刚才也说过了,由于单机应用程序的快速发展, 一个计算机形成了多层结构 如下面这个样子
其实随着编程的提高,应用程序的逐渐复杂,计算机这一个 "节点" 其实是分为多个层次的
如果要实现互联网的通讯,必须要考虑到所有层次的综合的通讯才可以.
由于计算机作为一个整体而言,分成树形的 多个层次
需要说明的问题是:
由于编程的不断发展,使得计算机被从底层开始,层层向上封装起来,直到应用程序的出现 (即便是在应用程序中 也会有底层 和 高层)
这种层层向上包装 堆叠带来的好处 就是计算机的使用难度越来越下降了, 现如今计算机已经被封装的太完美,以至于一个完全不懂的技术的人 简单教一下, 用鼠标也能轻松给计算机下达工作指令
然而 我们目前关注点 是在网络通讯上,这种层层向上包装,如果想实现通讯 ,就必须充分考虑到 每一个层次,且还要考虑到 层与层之间的传递
作为一个不懂技术的用户来说,他处于计算机的最顶端,也就是最高的应用层之上,他只会在这个层次上 傻瓜式的操作
我们就拿 发送一封邮件来说吧 :
比如 普通用户 就知道写好一封邮件,然后点击 发送按钮 (傻瓜式的操作),然后剩下的他就全不管了(他也根本不懂后面会发生什么事)
邮件的发送 一样属于网络通讯 , 普通用户可以不管背后发生的而事,但是作为技术人员的我们,却不能不管(用户是上帝啊)
回到正题上,用户在最顶端发起傻瓜请求(网络通讯请求), 这个请求发生在 最上层的 应用层A
请求的传递,需要从这一个层次开始,一路向下走 (一路打穿下去 看左边的绿色向下箭头),到达左边的最底层
到达最底层之后,向右边传递 (计算机网络的中通讯 真正背后是发生在最底层的 ,用户感知不到 并不代表就没有)
从最底层到达右边后(这里先简单理解为 右边的计算 代表接受邮件的傻瓜用户), 从底层传过来的 还得向上再打穿,一直打穿到右边的 最高层 应用A
这时候,右边的傻瓜用户 才看到了 刚刚发送过来的邮件
我们之前也跟大家说过, TCP/IP是一个协议的族群 ,它依然需要正确的指引路径 才能真正完善 且普及开来
\[反向代理的重要性 \]
很多朋友 都知道反向代理的概念, 但是并不是十分清楚其实质作用
我们就基于LNMP的环境进行讲解, HTTP的请求道来后,需要先经过 nginx 处理HTTP协议 以及静态内容
如果请求中有动态内容,则反向代理到 PHP(代码层)进行处理
关键也就在于此处
Nginx可以做七层负载均衡,其实负载均衡的基本功能 也是归属在反向代理之中
反向代理的资源消耗 要远远小于 PHP代码层的资源消耗 (Nginx高并发处理,资源开销很小)
所以,我们希望的就是 当attack请求到来时,最多控制在反向代理为止,不让其连带到 PHP代码层
尽可能切断这种 关联
但是 这种切断 需要判断请求的真伪 这是一个疑难问题
![](assets/1593412729-6d4ed112fd3a75877f82cd942feb7283.png)
\[提出高层埋点的方式,nginx+lua实现智能反向代理\]
问题在这里了,关键是怎么甄别来到的请求是善意的,还是恶意的。我们之前学习过的防火墙比如iptables这种,已经没有办法实现
我们得求助于更高级别智能的方法
首先,之前也说过 CC Ddosattack 是模拟真实用户请求
想通过很简单的方法,例如 用防火墙加个 IP黑名单的做法 是行不通的
IP数量庞大,且动态改变 或者IP伪装
既然CCattack处在七层,那么我们应对的方案 也需要在七层中 去想办法
我这里分享的一种 甄别的方法 ,叫做 埋点七层摸手
什么意思呢?请看下图
![](assets/1593412729-649dae0ed96a02572260621484b9f12b.png)
如上图所示:
我们在客户端的HTTP请求中,刻意加入几个参数, 然后计算这几个参数的md5值. 然后,服务端的反向代理中,加入判断的代码
这段代码会验证客户端是否正确提交了这几项参数,并且在服务端重新把这几项参数做一次md5值的验证。如果通过才会继续提交给后端的AP服务器,如果不通过则直接返回403错误码
在这几项参数中,需要加入一个动态参数,还有一个暗扣参数,这是什么意思呢?
动态参数:每一次客户端提交上来的这个参数都会不一样,比如:可以按时间的推移,把秒或者毫秒计算成一个参数,让它时时刻刻都在改变
暗扣参数:有一个参数是客户端和服务端藏起来的,也就是不直接体现在URL当中,而是由程序员私下定制,放在代码中
\[具体实现方法和部分代码\]
我们的nginx默认是不支持lua的,需要安装额外的模块
请按照如下的方法来操作安装
最后,我会给出大家一段lua的代码,模拟一个请求如何被lua判断,善意或者恶意请求,只要是不符合lua逻辑中的规则,请求就会被返回客户端
Nginx安装lua支持
需要LuaJIT-2.0.4.tar.gz,ngx\_devel\_kit,lua-nginx-module
1.下载安装LuaJIT-2.0.4.tar.gz
2.下载解压ngx\_devel\_kit
3.下载解压lua-nginx
4.下载安装nginx
```plain
location =/url_token {
set_by_lua $res "
local hash = 'xxxacgjfja199950ss'
local time = ngx.arg[1]
local lat = ngx.arg[2]
local lng = ngx.arg[3]
local TTtoken = ngx.arg[4]
str_join = hash .. time .. lat .. lng
token_by_sum=ngx.md5(str_join)
if token_by_sum == TTtoken
then
return 0
else
return TTtoken
end
```
如上就是代码了,总体来说就是判断客户端提交过来的http参数。如果两边计算的数值不相等,就会返回403,而不把请求转交给后面的nginx了
所以说ddos attack就会被我们的nginx+lua反向代 理屏蔽在外了
+228
View File
@@ -0,0 +1,228 @@
## 22.做好Nginx的监控-上篇
\[引言部分\]
当我们搭建好一整套的nginx集群之后,它就开始了每日繁忙的处理各种http请求了
现在的问题是,我们怎么才能确定所有的nginx服务都运转正常呢?难道每天24小时盯着页面和日志嘛?当然是不可能的,这就要依赖监控技术了
首先 无论你现在从事运维行业 处于哪一个阶段 , 监控都是最重要的核心工作内容之一
说监控是运维的第一核心也不过分
因为 系统的稳定运行 决定了运维的最高价值,而监控 是保证系统稳定的第一步 (也就是发现问题)
在运维行业流行这样一句话
您在一家企业中 如果真的把监控做到 100%到位了,什么样的问题都能即时的发现 并 预警(报警系统 跟 监控系统 虽然是一体工作的, 但是从技术和集群角度讲 是分开的 不可混为一谈 后面会谈到报警)
那么 这家企业的运维工程师们就成了最幸福的人,每天来了 就一件事 打开监控图 看看有什么问题 然后各种去跟开发较真儿 ^\_^
(实际的现况是 绝大部分公司企业 别说100%监控了 ,就连能达到50%程度的 都少之又少 ,所以 有大量空间去提高)
这句话说的有点偏激,但是也有一部分道理,它充分体现出了 监控对整个运维体系 集群架构的 核心作用
所以说 对于监控 我们绝对不可等闲视之
\[讲一讲经典老牌监控 Nagios\]
Nagios 经典老牌子监控,伴随着一代运维工程师们一路成长,即便到了现在 也广泛在企业中使用
虽然现如今的新监控产品 层出不穷,但是Nagios始终有这一席之地 除了工程师们的情怀以外, 还有什么因素呢?
Nagios有三个特点决定了它屹立不倒的优势
第一: Nagios跟其他监控产品比起来 是部署和配置最便捷 没有之一,且最容易入门的开源监控产品. 企业的老总们 最喜欢的就是省时快速的完成工作 高大上的技术方案对老总来说如浮云, 从成本节约的角度 -> 光从节约员工时间成本来说 Nagios在这方面就无可替代
第二: Nagios对于各种基础服务层面的监控 就是两个字 "完善" ,这得益于 Nagios的很多对于监控项目的基本计算方式都十分的直接有效,且非常符合一般人们对于"监控" 的实现理念.
第三: 新的监控产品 虽然很多, 但是这里有一个奇怪现象: 一般企业中面试运维工程师/架构师 , 到了监控这里 不问别的 就爱问 Nagios相关的问题 (记得我之前 就两次被面试官问道同一个Nagios问题 : 请你解释一下, Nagios 和 NRPE的工作模式和关系 .. ), 而像现如今 炒的很热的 Zabbix 和 Prometheus 从来没有被面试官问到过,连提都不提。 说到这里 大米哥个人认为 是由于Nagios对于各个企业来说,已经是用来考核一个合格的运维工程师的基本素养的指标了
\[Nagios的两种核心工作模式\]
nagios的第一种工作模式: 远程直连采集 工作模式
我们来看下面的一张图
![](assets/1593412758-12b276e96d30b6b14b17aee1cada2104.png)
上面这张图 解释Nagios的第一种工作模式
<远程直连采集 工作模式>
什么意思呢? Nagios的监控服务器端 需要采集被监控机器的 数据
而被监控机器上的一些数据 例如 :
IP地址通不通
端口通不通
HTTP页面是否可以正常返回200
等等 例如这样的监控数据, 我们称它们为 远程直连可获取 型的数据
这样一来,对于Nagios服务端来说,就很轻松的 直连上去 就能获取需要的监控数据了
比如:如果要知道一个被监控机器 是不是正常连着网呢, 那就直接发送个ping (icmp)上去就知道了
如果想知道一个被监控机器 上面的nginx页面是不是正常工作呢, 那就直接发送个 curl (GET) 上去就知道了
这就是Nagios的第一种工作模式: 远程直连采集
* * *
接下来咱们来讲讲 Nagios第二种工作模式: 桥接间接采集
我们来看下面一张图
![](assets/1593412758-7bda9f5b8d56a208ea471de6d19db171.png)
上面这种图 需要采集的监控数据 例如
CPU负载,硬盘剩余,进程存在 等等 , 类似这样的数据 本身并不是一个"服务"
什么意思呢? 好比硬盘剩余多少,这是一项客户端Linux系统上的自身的状态
本身不像 SSH服务一样 可以被外界直接访问到 ,如果nagios服务端 想获取客户端硬盘状况
没有办法直接从网络连上去获取,那怎么办呢?
这就要提到 Nagios的一个扩展功能 , NRPE组件了
请看如下这张图
![](assets/1593412758-55f53195003b3acced4ff949dac89588.png)
有了nagios NRPE组件后, 其在客户端上 先收集客户端硬盘状况信息
然后 再由Nagios服务端 连接NRPE来获取该监控数据 这样的方式 就称作桥接方式获取
到这里,Nagios的两种最重要的获取监控数据的模型 我们大家就知道怎么回事了
下一个小节起 咱们正式开始安装Nagios & NRPE
请先做好Linux下时间同步
接下来 Centos7 安装repl ,之后直接就可以安装 nagios , nagos-plugin , nrpe
nagios本身不带web server, 所以我们安装 httpd (apache)来实现webserver
安装完毕后,nagios会自动将需要的配置文件 加入到httpd中去
```sql
yum -y install epel-release
yum install httpd
yum install nagios* nrpe
```
之后 我们把两个服务都启动
```sql
systemctl start httpd ; systemctl start nagios
mkdir /var/www/html ; chmod -R 777 /var/www/*
```
然后查看一下 httpd启动端口, 以及 nagios进程
nagios启动后并没有端口 而是以cgi后端进程的方式启动
![](assets/1593412758-ee35303db368046030e19c75e79484bc.png)
启动之后 我们来设置一下nagios的初始登录密码
在安装好的Nagios服务端上, 我们先找到如下的路径
(注意:这里的配置路径 是遵循Centos7.x yum安装的形式 ,如果是源代码安装nagios 路径会略有不同)
如下便是 我们要找到的配置路径 可以看到已经有好多以cfg结尾的默认配置文件了
不过 这些其实都是一些自带的模板 我们当前用不上,我们先关注 localhost.cfg
\[root@server01 nagios\]# cd /etc/nagios/objects/
commands.cfg localhost.cfg switch.cfg timeperiods.cfg
contacts.cfg printer.cfg templates.cfg windows.cfg
接下来咱们先学习 如何在Nagios中定义"节点" , 这是重要的第一步
什么是定义节点呢? 其实就是 先把每一个需要被监控的机器 初步加进来
我们再localhost.cfg模板中 可以看到如下这样的配置
```plain
# Define a host for the local machine
define host{
use linux-server ; Name of host template to use
; This host definition will inherit all variables that are defined
; in (or inherited by) the linux-server host template definition.
host_name localhost
alias localhost
address 127.0.0.1
}
```
这个是nagios 安装好以后, 自定义的第一个节点,其实也就是 nagios机器本身 (127.0.0.1)
define host 是对一个被监控节点的整机定义 , 且每一个被监控的主机(节点) 都需要挨个这样定义才可以
接下来 我们把上面这一段 复制一下 变成如下的样子
我们添加 除去本机以外的 第一台被监控机器
```php
define host{
use linux-server ; Name of host template to use
; This host definition will inherit all variables that are defined
; in (or inherited by) the linux-server host template definition.
host_name server02.example.com ; 被监控节点 起一个名字 是唯一标识符号, 不可以重复, 后面会被反复的调用,名字 可以随便取,建议FQDN
alias server02
address 192.168.100.108 ; IP是多少 ,定义是多
}
```
这里需要说明一下, use字段这里先不要动,这是一个模块的定义,因为有其他的连带关系 如果随便修改会造成报错
我们就沿用已有的 linux-server这个模块即可,后面我们会讲到这里
接下来 host\_name , 重要的关键字, 给一台需要被监控的机器 起个名字, 这个名字是作为其在nagios整个监控中的 唯一标识符
并不仅仅是个名字而已, 后面会大量被其他地方调用, 所以这里 我们就严格按照 被监控机器的 FQDN来设置
alias 这里就只是个 节点的别名了,可以随意修改成你想起的名字
最后, address这里 又一个重要的关键字, 就是被监控节点的 IP地址设置, nagios本身并不具备 DNS解析的能力
设置的host\_name 必须要有真实对应的 IP地址, 不然nagios是找不到 被监控机器的
添加好上面这一段以后, 重启nagios,不需要重启 httpd
```coffeescript
[root@server01 objects]# service nagios restart
Redirecting to /bin/systemctl restart nagios.service
```
(注意 如果有语法错误的话,重启会造成失败)
接下来 我们来到nagios的页面 刷新以后,我们会看到 刚刚添加的这一台机器
以灰色显示在下面
灰色是机器被刚刚新加入nagios的时候,会处在一个 PENDING的阶段(UP)
也就是说 nagios目前仅仅是添加了这个节点,还没有获取到监控数据
过一小会 就会转变状态了
![](assets/1593412758-b6662cfcee1b78e986363d4846efe291.png)
然后我们再次刷新页面 就可以看到如下了
被监控机器 由灰色 转变为 绿色,说这个机器已经被监控起来了 并且目前是正常的
define\_host添加一个新节点后,其实就默认加入了 对这一个节点的 ping监控,机器是不是通的
(注意,如果你的配置文件中 IP地址写错了,或者这台机器 目前本身就ping不通,那么这里会显示红色,进入报警状态)
我们会定义节点之后, 我们继续把集群里的其他的 机器 都分别加上监控
```plain
define host{
use linux-server ; Name of host template to use
; This host definition will inherit all variables that are defined
; in (or inherited by) the linux-server host template definition.
host_name server02.example.com
alias server02
address 192.168.100.108
}
define host{
use linux-server ; Name of host template to use
; This host definition will inherit all variables that are defined
; in (or inherited by) the linux-server host template definition.
host_name server03.example.com
alias server03
address 192.168.100.109
}
define host{
use linux-server ; Name of host template to use
; This host definition will inherit all variables that are defined
; in (or inherited by) the linux-server host template definition.
host_name server04.example.com
alias server04
address 192.168.100.110
}
```
得到如下的监控图
![](assets/1593412758-fa87e75d882475450afddf7795b6a59a.png)
掌握了 Nagios配置的第一步 ,也就是监控节点的定义
有了这个好的开始,就可以继续向下推进了
+334
View File
@@ -0,0 +1,334 @@
## 23\. 做好Nginx的监控-下篇
\[配置service服务的监控\]
通过上篇的学习,我们已经可以定义出监控的基本项目
不过如果想监控nginx这样的服务,我们还得学习服务的监控方法,如何在nagios中定义
定义好节点之后, 接下来 我们继续学习 如果定义一个host\_group 也就是组的定义
在Nagios中 各个节点的定义毕竟是分散的,我们可以将属于一类的'节点' 统一成一个'组'
接下来 我们来看下 配置文件的定义
```plain
define hostgroup{
hostgroup_name linux-servers ; The name of the hostgroup
alias Linux Servers ; Long name of the group
members localhost , server02.example.com , server03.example.com,server04.example.com ; Comma separated list of hosts that belong to this group
}
```
如上这一段 添加到 localhost.cfg , 就是把之前的四个节点(包括本机) 都加入到一个host\_group组当中
hostgourp\_name定义组名称 很好理解
alias 则是起一个组别名
重要的是最后一项 members的定义
members 后面接上各个节点的 host\_name, 注意 这里的host\_name 就是节点当中的 host\_name, 必须一致 ,且不可以使用别名
用逗号分隔开, 不然语法会报错
之后 我们来到Nagios页面 看一下定义的组
左边找到如下 Host Groups
![](assets/1593412784-fb3e03bbf49cef9c3a68c3b7baba0f54.png)
之后 右边会显示这个组的信息,以及组的成员
组的定义 在企业的使用中 非常实用,企业中往往 会有少则10多个,多则上百个 大大小小不同的集群
每一个集群 都可以单独定义一个Nagios组名称, 比如 webserver\_group / DB\_group 等等这样
在查看监控的时候,可以快速定位集群
学会了host\_group定义后,咱们再学一项新的定义内容
就是 Nagios service(服务)的定义
定义好 节点,定义好组 这两项之后,Nagios也仅仅提供了 整机监控(也就是ping联通性)
可是我们还需要监控更多的东西,比如 我们也要知道 被监控机器上 SSH服务是否可用
那接下来 就要依赖Nagios service的定义了
我们来看如下这一段,取自localhost.cfg中
这一段就是默认模板提供的 service定义方法 用来监控机器的各类服务(软件)
```plain
define host
{host_name }
define service{
use local-service ; Name of service template to use
host_name localhost
service_description SSH
check_command check_ssh
notifications_enabled 0
}
```
基本的写法跟 定义 define\_host 差不太多,不过关键字有了变化
use 字段我们保留不更改
host\_name: 这里 写上在 define\_host当中定义的 节点名字, 依然注意 必须跟之前的host\_name一致 不然Nagios找不到
所以,这里也再次证明了, 定义节点是多么的关键,后续会大量的 二次引用
service\_description 这里仅仅是 这一项服务监控的注解, 可以随意写成你要的名字
check\_command 这是最关键的一项了,定义这一项服务监控 具体要监控什么内容,调用什么监控脚本去执行 (后面我们会讲到 脚本自定义)
notifications\_enabled 是否开启提醒功能。1为开启,0为禁用。也就是是否 开启报警 针对这一项服务的监控(我们后面会讲到报警 Pagerduty)
学会了nagios service的定义方法后, 接下来 咱们来做一下扩展
上面SSH的实例 ,我们可以看到如下
host\_name localhost
其实是针对 本机(Nagios 服务端本机) 进行的service 监控
接下来 咱们再复制添加一份SSH监控,并改成监控其他客户端
```plain
define service{
use local-service ; Name of service template to use
host_name server02.example.com
service_description SSH
check_command check_ssh
notifications_enabled 0
}
```
上面 我们只把host\_name 改成 server02.example.com即可 (依然注意 必须使用完全一致的 host\_name)
重启nagios服务后 再来看一下 nagios的主页
![](assets/1593412784-aadd7e5d226ad57782c12f05c52eeb0d.png)
我们发现 点击service之后,右边出现了 两台机器的service监控
localhost 和 server02
所以 我们就可以以此类推 后面继续添加其他的机器 陆续进入服务监控中了
接下来 我们除了SSH service之外,再来添加一项其他的监控
我们来在 localhost.cfg 后面 添加如下 两项配置 (第一项 其实已经是自带的了)
```perl
define service{
use local-service ; Name of service template to use
host_name localhost
service_description HTTP
check_command check_http
notifications_enabled 0
}
define service{
use local-service ; Name of service template to use
host_name server02.example.com
service_description HTTP
check_command check_http
notifications_enabled 0
}
```
上面这两项 用来监控 本机和 server02的 HTTP服务
其中红色标记的地方 就是新改的
这次因为需要 监控http服务了, 所以使用 check\_http 作为监控命令
这里添加的时候需要注意如下 两项
1) check\_http 本身是监控 http url是否可以正常访问的, 默认是 以 [https://IPADDRSS](https://ipaddrss/) 来访问 (80端口)
其中的IP地址 就是你在define\_host当中定义,并且延伸下来的
添加这两项监控之前 你需要保证 机器上 确实开启了 工作在80端口的 web server (apache 或者 nginx 无所谓)
2) check\_http 默认是监控 例如 [http://127.0.0.1/index.htm](http://127.0.0.1/index.htm) (或者是其他web server中定义的index.xxx)
所以 你还必须保证 在你的web server配置的 root路径下 要存在首页(默认apache是在 /var/www/html/ , nginx在/usr/xxx/shar/html 版本不同 请各自查看),不然 也是监控失败
3) 一旦check\_http 访问失败,不能返回200 ,那么监控主页 就会显示成 warning 或者 failed
正确的监控图 如下
![](assets/1593412784-98c85ef2189f235cdd998098a1cb3205.png)
\[服务监控的进一步探索\]
上一个小节最后, 我们留下了一个问题,service监控 如果需要变通的时候 怎么办呢?
接下来 我们就得 深入一步 来学习 nagios的插件了
```plain
define service{
use local-service ; Name of service template to use
host_name server02.example.com
service_description HTTP
check_command check_http
notifications_enabled 0
}
```
上面这一段 service 配置中,红色标记的部分 ,之所以能直接调用check\_http
并不是天上掉下来的 ,而是有依据的
这个依据就是 nagios的插件 (我们之前安装的时候 不是安装了很多 nagios-plugins吗 这个就是插件)
nagios 定义service中 check\_command 其实就是通过调用插件当中 预先设置好的 这些脚本( check\_http其实就是个脚本)
才能实现监控的
我们接下来 分如下几步来分析 这些脚本和插件的关联
1) 首先 我们先执行如下命令行
rpm中 先找到 跟http相关的 nagios插件
\[root@server01 nagios\]# rpm -qa | grep nagios-plugins | grep http
nagios-plugins-http-2.2.1-9git5c7eb5b9.el7.x86\_64
check\_http这个脚本 其实就来自于 这个插件
2)接下来 我们来查看 这个插件中 都包含了什么具体文件(脚本)
\[root@server01 nagios\]# rpm -ql nagios-plugins-http-2.2.1-9git5c7eb5b9.el7.x86\_64
/usr/lib64/nagios/plugins/check\_http
如上就可以看到 ,其实 check\_http 是放在了一个 默认的 nagios插件路径当中了,作为脚本被 nagios调用
3) check\_http这个脚本找到后, 我们在Linux本地来试一下
\[root@server01 nagios\]# /usr/lib64/nagios/plugins/check\_http -H 192.168.100.108
HTTP OK: HTTP/1.1 200 OK - 275 bytes in 0.002 second response time |time=0.002124s;;;0.000000 size=275B;;;0
哦 我们发现 原来这个脚本 不光可以被 nagios的 check\_command直接调用
它自身还可以直接被 咱们执行 并能返回结果
4)接下来 我们本地用这个命令 来变通一下
我们把 server02的 httpd 端口改到 8080
改好之后 原本的nagios 配置的check\_http 就监控不到了
于是乎 nagios主页会变成这个样子
![](assets/1593412784-fd2767ff0995f8fd6935fa0969ef89ed.png)
那么怎么办呢? 我们现在Linux本地 试试 check\_http 脚本 是否支持端口改变
\[root@server01 nagios\]# /usr/lib64/nagios/plugins/check\_http -H 192.168.100.108 -p 8080
HTTP OK: HTTP/1.1 200 OK - 275 bytes in 0.002 second response time |time=0.001994s;;;0.000000 size=275B;;;0
如上图命令行的最后, 其实可以 添加一项参数 -p 来改变端口
这样以来, check\_http 就可以监控其他的端口了
但是这里咱们又遇到一个疑难问题了, 虽然这里我们使用check\_http脚本本地执行 可以实现了
但是 本地执行脚本 跟 nagios service的配置 目前还没有关联起来啊,还是起不了作用呀
那咱们下一个小节开始 ,咱们继续学习 如何建立这种关联
首先我们先 找到这个配置文件
/etc/nagios/objects/commands.cfg
在找到 其中这一段配置
如下这一段配置,其实是把 check\_http定义出来
名称 以及 使用方法 参数如何调用 其实都是在这里设置的
```ruby
# 'check_http' command definition
define command{
command_name check_http
command_line $USER1$/check_http -I $HOSTADDRESS$ $ARG1$
}
```
所以说 ,在 service(define\_service)中的 check\_http ,其实先要在 command.cfg中定义出来(nagios => \*.cfg)
define command 就是定义一个监控脚本的关键字
接下来 咱们来看其中的这一行
command\_line $USER1$/check\_http -I $HOSTADDRESS$ $ARG1$
这一行 就是咱们要找的关联地点了, 我们上一个小节中插件所在的路径(rpm => /usr/lib64/nagios/plugins/check\_xxxx),其实存在 $USER1$/ 下
关联这个命令之后,咱们还得 给这个命令 配置参数定义
我们把 这里nagios\_commonad定义的命令 和 本地命令行使用脚本的用法 放在一起
本地命令行是这么用的 /usr/lib64/nagios/plugins/check\_http -I 192.168.100.108 -p 8080
command\_line中 是这样的 command\_line $USER1$/check\_http -I $HOSTADDRESS$ $ARG1$
Nagios define\_host(IP)
我们注意 两行后面的 红色部分 ,这下其实不用我说 大家也看明白了
参数在两边的定义 是一一对应的
先是 -I + IP地址 , 再是 -p + 端口
不过这里有一个地方需要特别的注意
$HOSTADDRESS$ 这里 是取自service定义当中 host\_name机器对应的 ip地址
而$ARG1$ ,比较特殊, 这个参数的值 是需要在 service中自行定义添加进去的 (define\_service , ARG1 传参数)
这么说 可能还是不太好理解, 没关系 咱们拿一段配置来给大家讲解
接下来 咱们来看一下 这样一段 service 的定义
\[root@server01 nagios\]# vim objects/localhost.cfg
```plain
define service{
use local-service ; Name of service template to use
host_name server02.example.com
service_description HTTP
check_command check_http!-p 8080
notifications_enabled 0
}
```
我们把之前定义的 check\_http 的配置 改成如上这个样子
我们注意一下 红色标记的部分
check\_command check\_http!-p 8080
这就是在 service中 调用监控脚本时候,添加额外参数的方法 使用 ! 分开
!每一项参数 都用这个个离开 , 参数的使用方法 跟本地命令行 是一模一样的
不过这里细心的朋友 会有这样的一个疑问
check\_command check\_http!-p 8080
之前 check\_http 在使用的时候,不是用了两个参数吗? -I ipaddress , -p 8080
怎么在这里 调用的时候 只有一个-p 参数呢?
说到这里 其实就回答了刚才的疑问, 现在明白了 刚才的两句解释了
$HOSTADDRESS$ 这里 是取自service定义当中 host\_name机器对应的 ip地址
而$ARG1$ ,比较特殊, 这个参数的值 是需要在 service中自行定义添加进去的
这几个配置文件 和插件 直接的关系 可以用如下图 来表示
![](assets/1593412784-24c589c4faf289c29838c73664450fe1.png)
接下来 我们重启 nagios来看下 监控主页 是否已经对HTTP的监控改了 端口
\[自己编写任意的监控脚本\]
我们注意到没有 之前我们在 使用nagios的时候 添加监控项目 要么是 nagios自己自带的
要么是 nagios\_plugins\* 插件包提供的
其实 Nagios插件 是完全支持 自己动手编写的 只要你写的脚本 符合了一定的规则 那么 就可以被Nagios识别了
接下来 我们来看一下如下这一段 shell 代码
这就是 一段自行编写的 nagios监控脚本
脚本实现的内容很简单, 就是通过 netstat命令来获取 Linux系统中的 等待链接数(waitting\_connections)
如果大于1000就报警 ,小于1000 就属于正常
Nagios可以识别4种状态返回信息,即 0(OK)表示状态正常/绿色、1(WARNING)表示出现警告/黄色、2(CRITICAL)表示出现非常严重的错误/红色、3(UNKNOWN)表示未知错误/深黄色
Nagios根据插件返回来的值,来判断监控对象的状态,并通过web显示出来,以供管理员及时发现故障。
![](assets/1593412784-136b8848ed90601c17748e71c775e63c.png)
写好脚本后,接下来 先不管Nagios , 我们本地测试一下
本地测试好了脚本之后, 下一步, 咱们找到nrpe的配置文件, 添加一项新的命令(nrpe)
```plain
[root@server02 nrpe.d]# cat /etc/nrpe.d/lcgdm-common.cfg
command[check_cpu]=/usr/lib64/nagios/plugins/lcgdm/check_cpu
command[check_network]=/usr/lib64/nagios/plugins/lcgdm/check_network
command[check_process]=/usr/lib64/nagios/plugins/lcgdm/check_process -p rfiod,globus-gridftp-server
command[check_hostcert]=/usr/lib64/nagios/plugins/lcgdm/check_hostcert -C /etc/grid-security/hostcert.pem -s
command[check_waiting_connect]=/usr/lib64/nagios/plugins/check_waiting_connect
```
回到服务端 nagios 服务端添加
```plain
define service{
use local-service ; Name of service template to use
host_name server02.example.com
service_description WAITING_CONNECTS
check_command check_nrpe!check_waiting_connect
notifications_enabled 0
}
```
接下来 保证 客户端 nagios用户 有权限执行 监控脚本
然后来到服务端 检查
```coffeescript
[root@server01 objects]# /usr/lib64/nagios/plugins/check_nrpe -H 192.168.100.108 -c check_waiting_connect
OK,waiting_connections < 1000 low
```
如上就是脚本的编写方法了
@@ -0,0 +1,191 @@
# 3.从配置文件开始 理解Nginx的模块化
## 3.从配置文件开始 理解Nginx的模块化
在运维界有这么一句话叫做 , 配置文件是源代码的缩影 ,看不懂源代码没关系 配置文件一定要能看懂
上一个篇幅中 大米带着大家 照葫芦画瓢的 把nginx和一个首页跑起来了, 但是这在学习NGINX的万里长征中 仅仅迈出第一步
(最多也就算是个 NGINX版的 HelloWorld 而已)
所以 为了继续深入学习 咱们就从头好好认识一下NGINX的配置文件吧
说配置文件是缩影 是因为 配置文件才是决定了 NGINX到底是怎么如何运行的 , 大米之前跟大家说过 NGINX是一个高度模块化的开源软件对不? 那咱们现在就针对这个"模块化" 来一探究竟
首先 还记得第一次 看到所谓的"模块化" 是在哪里?就是如下图 咱们刚刚解压完nginx的src目录下(存放着源代码)
![](assets/1593412221-d8e2b505fca143cdb4a7b86ce741b1fa.png)
这些源代码 按照"模块"划分成好几 目录 ,每一个目录对应一个模块的名称 其中 可见的最重要的有
core : 核心模块 event : 事件模块 http:页面模块 这几项
我们现在知道了 代码都是按模块来划分, 如果要让NGINX正常提供服务 就必须得恰当的调用起一个一个模块 才行 对不?
提问: 请问老师 怎么调用啊 ? 我看不懂代码
回答: 我不是说过了吗 代码现在看不懂无所谓, 想调用模块 咱们用配置文件即可
接下来 我们来看一下NGINX的配置文件组成结构
我们从NGINX最重要的 主配置文件 nginx.conf来一点点看配置
首先是 全局配置指令 我们称作 main block (主配置段)
主配置段的 配置指令 一般就放在nginx.conf前面 就如下所示
```plain
user nobody;
worker_processes 1;
error_log logs/error.log;
pid logs/nginx.pid;
```
这种 主配置段的配置指令 不需要放在花括号中 {} 而是直接 由 配置项 + 配置值 组成
接着,其他的 都是由模块配置段 组成
比如在 nginx.conf中 首先第一个可以看到的 模块配置段 就是event{}
模块配置段 按照一个一个模块的名称 + 花括号 来定义
然后 花括号的内部 再一行一行的添加 这个模块中的 各种配置项+值
event{
...
}
event是 nginx的事件驱动模块 主要是提供面对用户的并发响应相关的功能(等我们后面 深入讲解NGINX的底层运行机制的之后 会返回头来看 会更清楚)
接下来的 模块配置段 就是
http {
...
...
}
HTTP模块 是最重要的 给nginx提供 web请求处理的模块 , 而且这个模块中 还会继续嵌套其他的重要模块
比如
http{
server {}
}
如上所说的 main block , event{} , http{} 这三部分加在一起 就构成了nginx最主要的配置 因为没有它们 nginx就没法正常使用了
(nginx还有其他很多扩展的 模块 但不是最必要的 )
nginx的模块配置段 有两个特点
第一 : 部分模块中 还可以嵌套其他的模块
第二 : 模块中 的配置值 有的时候 并不仅限于一个模块中来调用,也支持在其他的模块中调用
比如 还记不记得 我们之前讲过的 root 配置项? (用来定义 网站资源的根目录在哪里 )
这个root 即可以定义在 location{} 模块中, 也可以定义在 server{} 模块中
提问: 老师啊 我现 在知道配置文件大体什么形式了 不过 这些配置中的 各个模块 以及模块中的配置项 都是用来做啥的啊 怎么入手才好呢
回答: 俗话说 书到用时方恨少 NGINX的模块和各种配置项很多很多 一个一个光秃秃的死背概念是没法学会的, 咱们下面 尽量采用实验的方式 来对一些重要的配置项进行认知 很多功能只有到了遇上需要的场景的时候 咱们才能体会它的作用
首先 咱们先把 nginx.conf备份一下 然后再整个清空 然后一项一项的添加进去 以此来看看NGINX的表现如何
![](assets/1593412221-acd5cb58aa3000b3d663e0ef14d9f80a.png)
nginx.conf 内容清空后 我们使用 nginx -t (-t 是检查配置文件正确与否的 以后要反复使用) 提示配置文件出错
events{} 模块没有被定义哦.... - -
那看来 NGINX中 最少最少要调入一个 events{} 事件处理模块 才可以 ,不然连启动的资格都没有
接下来 咱们把 events{} 加上一个配置项 添加进去
events {
worker\_connections 1024;
}
然后咱们检查一下语法 再尝试启动
![](assets/1593412221-e56729fcfb52c4f78ad6616db4903578.png)
现在是正常了,说明NGINX启动 最少需要一个events{}模块 不然就没有 响应用户请求的最基本功能了
接下来 worker\_connections 1024 又代表什么意思呢 ?
别着急 我们紧接着 先在 nginx.conf 最上面 再补充上如下一行
![](assets/1593412221-318cf6dd86baf24fff235e0780e23bbb.png)
这里我们通过 ps 看到 nginx启动后 一共有三个进程
一个master叫做主进程
两个worker进程 叫做工作进程
我们用下面一幅图来给大家解释一下 nginx的 基本工作原理
![](assets/1593412221-340dc54dcc4af6d32d81d41eddc01fda.png)
nginx采用 master/worker的形式来工作的 , 也就是说 通常情况下 nginx先启动一个 master进程 这个进程属于"纯管理"的工作
并不参与处理用户的请求的处理, 它是负责 调度开启workers , 加载配置文件,启动停止服务等等 。 而真正用来处理 HTTP请求 则全部都由workers进程担任
master进程只启动一个(一般拥有root权限) , 而worker进程则可以有多个
那么学员问了, worker进程 启动几个由什么决定呢? 另外 每个worker进程 能处理多少请求呢?
^\_^ 这就等于是回答了 咱们刚才的问题了
worker\_processes 2;
events {
worker\_connections 1024;
}
刚刚咱们加入的配置文件 worker\_processes 2 决定了 一共启动两个worker进程
而 worker\_connections 1024 设定了 每个worker进程 可以同时最多处理 1024个 HTTP请求
不过说到这里 咱们又遇到一个问题了 -\_-
现在nginx起来了么?
回答:起来了
那nginx现在可以处理请求么?
回答:处理不了
我们看下 Linux上面的情况
![](assets/1593412221-78f2671670e8306a6d757d70c1e8cf3a.png)
如上这张图看到了么, 我们的 nginx 连端口都没有开启 根本无法接受请求啊 就算进程起来了 也没有用处
那怎么办呢?
继续加配置文件啊 调用模块啊!
nginx想要拥有 处理http的基本功能 必须调用 http{}这个最大的模块 才可以的哦
另外 在http{}模块中 我们还需要再调用一个 server{}模块 用它来定义出一个 "虚拟主机"
虚拟主机的概念
虚拟主机,就是把一台物理服务器划分成多个“虚拟”的服务器,每一个虚拟主机都可以有独立的域名和独立的目录
或者说的再简单点,就是一个虚拟主机 就可以理解是 一个"网站" 多个虚拟主机 就可以是多个"网站"
nginx中 最少要有一个 虚拟主机 server{} 才可以正常工作
![](assets/1593412221-99c67e3a9437b24823452c1863dbe230.png)
接下来 咱们把 http{} 以及 server{}模块段的配置 补在后面 如上图所示
http{} 起头 先调用mime 是为了让nginx 不但可以处理纯文本 也可以处理多媒体
举个例子来说吧, 你平时访问一个网站的时候 难道只有文字?? 一定还会有各种图片 动画 flash 对不? ^\_^
接下来是 server{}模块中的 配置项
listen 80 ; 我们让这个 虚拟主机 监听在80端口
server\_name localhost; 这是虚拟主机名称 是个非常重要的配置项 当前我们先不太多解释 后面会详细说这里
root html; 这个我们之前在 创建咱们第一个 主页的时候 也介绍过吧, 就是设置 网站的主目录在哪里 主页面 就存在这个目录下的哦
好了之后 我们重启nginx
看下是否成功
@@ -0,0 +1,206 @@
## 4.从配置文件开始 理解Nginx的虚拟主机 和 DNS解析捆绑
【网址域名和DNS的来龙去脉】
我们在上一个篇幅中 从配置文件开始 逐步理解nginx的工作流程
到了这个篇幅中我们要讨论一个关键点 同时也是难点 那就是虚拟主机的概念 以及DNS
(对于初学者来说 这里的概念很不容易理解 但是一旦搞清楚了 收益会非常大)
请大家现在思考一个问题 我们平时是怎么访问某个网站的?
回答:输入网址呗 这还不简单么 比如 www.baidu.com . www.nginx.org 等等
但是你有没有想过 当你输入网址后 背后发生了哪些细节 到底是怎么就访问到某个网站上了
如果要搞清楚这个来龙去脉 先不说NGINX 咱们先从DNS开始
DNS是什么东东呢? domain name server 域名服务
说的简单点 就是IP地址才是我们真正要去到地方对不? 可是网站的IP地址几千万个上亿个 你记得住吗?记不住的
那自然就要有个方便记忆的名称 来替代繁琐的IP地址了 对不 这就是网站的域名
比如 www.nginx.org 就最终一定会 解析到一个IP地址上去了
之前咱们说过 网站的建设 一定会涉及 服务器和WEB服务 对不?
那么解析出来的IP地址 会直接对应的是一台物理服务器的IP地址 这是咱们最容易想到的
但遗憾的是 真实情况中 往往一个网址(尤其是比较大的网站的网址)对应的IP 肯定不是直接对应"一台"服务器 因为一个站点的流量是很大的 只可能用集群来搭建 , 所以 这还涉及到 负载均衡 VIP 等等的概念 我们还没学到 暂时先不讨论
咱们先考虑简单的情况, 用一台物理服务器搭建一个网站 有没有可能?
回答:如果是流量很小的网站 这是完全可以的
接下来 我们继续往下走
假设我们现在在公网搭建了一台服务器 公网IP地址A 并使用NGINX架设了一个网站(很简单的静态网站) 我们希望 别人可以通过 类似 www.abc\_test.com 能访问上去, 我们需要做的事 就是在公网DNS服务器上 注册我们的网站域名 把它指向解析到 IP地址A 别人就可以在世界各地访问到我们的首页了
(当任一一个人想访问你的网址的时候, 它输入 www.abc\_test.com , 会先向 公网DNS服务器 询问对应的IP地址是多少 然后才会访问到你的网址首页)
可是我们在学习过程中 没有这样的条件 而且 我们的 虚拟机也是在自己的电脑上搭建的 都是通过内网访问的
咱们现在 既没有公网IP可用 又没有DNS服务器可用
这种情况下 我们依然希望通过 用网站域名的方式来访问自己的站点 怎么整?
那么 接下来 大米就介绍给大家一个 在企业中测试 经常使用的方法 本地DNS解析捆绑的方式
不管你用的是windows 还是 Mac 或者是Linux
都会有本地的DNS解析配置文件 hosts 每个系统位置不一样 (具体的可以网上搜索)
因为本地hosts文件添加的解析记录 优先级是最高的 所以 一旦添加一个域名之后 操作系统就会仅仅使用hosts的记录 而不再理睬公网DNS服务器所解析的了
咱们举个夸张点的例子 , 比如 你自己使用NGINX搭建了一个首页 你想用 www.nginx.org来访问它 可不可以
(试想一下 www.nginx.org 这可是nginx官网的域名 你现在都可以借用这个名称 强行本地解析指向到你自己搭建的站点上了 )
比方说 我现在 就想用 www.nginx.org 来访问我自己搭建的nginx首页 , 我使用的是mac系统
那么 我就把 www.nginx.org 192.168.0.100 加入 /etc/hosts即可 很简单
【掌握NGINX虚拟主机 SERVER的概念 极其重要】
为什么前面 咱们花了一大段篇幅 来给大家讲 网站域名和DNS呢
那是因为 接下来 咱们就得用 上面的掌握的知识 来进一步 探索nginx中的 虚拟主机(SERVER)的相关用法了
咱们之前讲过 NGINX的模块化的设计,有了最核心的三个组成部分 main block + event + http 就可以开启NGINX的基本工作模式了
接下来 咱们看看请求到了NGINX后 是怎么被接手而后处理的
HTTP{} 模块 作为处理WEB请求的总入口 这个我们已经了解了
接下来 到了下一个关键 也就是 SERVER{} 模块
SERVER{}模块 是附属于 HTTP{}模块的一个极其重要的模块 它不能脱离HTTP{}而单独存在(HTTP模块 可以认为是最大的 最上层的 所有和处理web相关的其他模块 都属于是它的"下属")
我们上一个篇幅中 给大家讲过 NGINX的配置中 HTTP{} 怎么用 例如下面
http {
server {}
}
看到了吗 , server{}是http{}的下属 , 所以它必须在http{}大括号内部调用, 绝对不能"放出去" 单独调用的
接下来 咱们做个小实验 偏偏把SERVER{}模块 从HTTP{}中给移出去 放到外面去试试 看看NGINX什么反应
![](assets/1593412258-ff06bfee32144fc743bcab33bcd595db.png)
接下来 我们用 nginx -t 检查一下语法 看看会怎么样哦
![](assets/1593412258-05389c352fb5a73534461054dbd941d9.png)
看到了没! ? 错误提示说 server{}模块 不允许被定义到这个位置 意思也就是说 server{}模块 不可以脱离http{}模块 而单独调用的
明白了这个道理后,咱们继续往下推进 回到主题 server{}模块
server{}模块 之所以那么重要 是因为 它是作为 用户的web访问 到达nginx的 http{}模块之后 第一个接手的重要入口
为什么这么说呢 咱们先来看看下一附图
![](assets/1593412258-3bff77d9d6b3058806fb67ccd2958878.png)
在这幅图中 我们从左上开始看 看到什么? 对 就是用户输入的网址 URL
一切从网址开始 , 而你想想 为什么 输入一个网址 就可以访问到你想要的网站的页面?
那是因为 这平凡的网址中 包含了一些 非常重要的 关键信息 正是因为这些关键信息的存在 才能最终把你导向到 正确的地方
首先 http:// 这个起头 说明咱们现在从浏览器发起的是 http协议的请求, 那接手人 就必须得是像nginx这样 可以处理http协议的软件 才行 对不?
再接下来 dami01.example.com 这个咱们之前说过 这是网址域名 需要先被DNS翻译成 IP , 然后才能到达咱的 服务器上对不?
然后 注意这个地方
![](assets/1593412258-290324d5ca48b236a70a4836a2cbce3f.png)
多了个80后面, 之前没有这么写过, 其实这才是 完整的网址域名的写法哦
域名+端口 (或者 IP + 端口 , 但是一般情况下 没人会这么用的!)
这个后面的端口 其实是所有网址在输入的时候 都必须存在的, 只不过 如果是80端口的话 默认你不写上 浏览器也会自动帮你补齐了
只是你没注意而已
接下来继续
有了域名了 -> 被DNS翻译成IP . -> 请求就到了 这个IP的服务器上了
接下来呢? 自然就是nginx要接手了 对不对 继续看图上
当请求到达NGINX后, HTTP{}模块 派遣出SERVER{}子模块 来进行第一步的应答
应答的关键 就是在于 分析 用户网址请求中附带的两个关键信息
(准确的说 应该是附带了三个关键信息 , 还有一个咱们后面会单独讲 也就是location 现在先不管)
dami01.example.com:80
第一个关键信息 : 域名
在这里其实起了两个作用 , 不仅仅只是 解析了IP地址 为了到达机器上
还有一个 非常关键的作用 就是访问的域名不同 SERVER{}模块提供的待遇就会不一样的
第二个关键信息: 端口
端口这个比较好理解 ,就不再多解释了
所以说 在HTTP{}模块中, 用于首次接手用户请求中 两个关键信息的子模块 就是由SERVER{}提供的了
根据用户输入的 域名或端口不同 , SERVER{}模块 就会给予不同的待遇(不同的处理方式)
(理解好这里 是非常重要的关键!!!一定要反复精读琢磨)
接下来 咱们看看 SERVER{}的一般写法
![](assets/1593412258-5cc27194aad54b03e64fbe9eb0a32176.png)
如上面这张图所示, SERVER{}模块中 三个起着决定性作用的配置项
listen server\_name root
listen是开放在哪个端口 , 这个比较好理解
server\_name . 这是关键,用户输入的 域名是什么 就在这里匹配
比如 当我输入网址 dami01.example.com 就匹配到 第一个server{}
当我输入网址 dami02.example.com就匹配到 第二个 server{}
然后 我们看第三个 root ,网站提供资源的根目录在哪里
其实说到这里 就很容易解释了, SERVER{}的任务 就是根据用户 输入的 域名或端口的不同 来提供不同的待遇
第一个SERVER{}匹配补上,就第二个 以此类推
但是总说"待遇"不同, 这个待遇究竟指的是啥啊? 那么root在这里 就是一个很好的回答
根据匹配的SERVER{}的不同, 那么 NGINX最终返回给用户的 页面就不一样了呗 不同的页面存放在不一样的路径中哦
所以 这就是SERVER{}的用途和工作机制, 大部分企业中的这样用法 都很类似
接下来 咱们来做实验
请先分别 在你的Linux上 创建类似如下的三个 目录,用来存放 不同的index.html 用来匹配不一样的SERVER{}
![](assets/1593412258-7f9f43275b8fb97e644eba1a92cfccd5.png)
然后 把三个index.html 修改一下 做个区分
比如这样
![](assets/1593412258-f87abc5602c7702cdb75a98fbd26361b.png)
这样一看页面效果图 就证明 匹配到了哪个 SERVER{}中的 SERVER\_NAME
最终的实验结果 就是下面这样
![](assets/1593412258-d36d7b6f9d27e2c2c0724cbb4af4360c.png)
看到了吗 , 同样是一台NGINX服务器, 输入的域名不一样 , 得到的结果就是不一样的
这里 再补充说明一下
在企业中 这样利用NGINX的虚拟机功能 是非常常见的
往往一台NGINX服务器上,都会配置一大堆不一样的域名, 但是这些域名 都解析到同样的一个IP地址上
根据域名调用的不同 则给予不一样的返回结果。
@@ -0,0 +1,364 @@
## 5.Nginx做反向代理和负载均衡 企业中必用(上篇)
[反向代理 NGINX的第二大核心功能 ]
通过之前章节的学习 我们对于NGINX如何处理HTTP请求 以及如何处理WEB页面 有了一定的认知
接下来 咱们进入另一个重头戏 就是 反向代理
看下咱们的标题~ 之所以说反向代理 是NGINX的第二大核心功能, 是因为NGINX在最初只作为处理"静态页面"的服务器 (静态我们已经懂什么意思了)
这个功能始终都是NGINX的第一核心, 而反向代理的功能 现在也是使用的越来越普及 甚至有跃为第一功能的可能性
(大米 曾经经历的几家公司,就出现过 集群中完全没有用到NGINX的 WEB处理功能,而仅仅使用NGINX作反向代理的例子)
那么要搞清楚 啥是反向代理,咱们总得先搞明白 代理是啥
所谓的 代理 代理 就可以理解为 代为处理 不是亲自上阵的意思 这个很好懂吧
(很多网上的资料 或者书籍 总喜欢 把正向代理 反向代理 透明代理 顺序讲一遍 大米不想这么混在一起讲 因为对于初学者 很容易搞混了 咱们先只说反向代理 因为反向代理 是用的最多最多的场景)
懂了什么是代理, 大米接下来 用最通俗的一句话 给大家解释 啥叫反向代理
所谓反向代理: 就是我们原本有一台服务器A , 上面建立了个网站的首页, 用户原本应该是直接访问到这台服务器上 对不? 有了反向代理 就不一样了 用户先访问到的 根本不是这台真正的提供页面的服务器A,而是先访问挡在前面的一台服务器B, 这台服务器B把请求 先揽到自己"头上", 再代为转交给后面 真正的服务器A, 完成一次请求。 而对于傻瓜用户来说 他根本就完全不知道 这个反向代理的服务器B的存在 以为自己直接就访问到了A
这么说应该很容易理解了吧
说的再白一点,就是反向代理 就挡在前面 先接活儿(接活不等于 直接干活 只是登记一下转交而已 :)) 然后再扔给后面的去处理 ^\_^
用下面的一张图 就很清楚说明 反向代理处在一个什么样的位置
![](assets/1593412292-311f3ea71e3fa34eff146ab2cc826956.png)
\[为啥得有反向代理?多此一举?NO NO NO !~\]
有的学员可能要问了, 反向代理是怎么回事 我是明白了 不过这么做的意义何在呢?
明明服务器A提供了网页,直接让用户访问它不就好了 为啥要多此一举 先发给前面的机器 再代理转发给后面的?
有这样的疑问是很正常的哦 接下来大米给大家来通俗解释一下
第一点 关于负载的问题:
我们在上一个单元跟大家提过,大部分情况下 当一个用户输入一个网址后,请求到达的地方往往都不是"一台"服务器,而是一个集群对不?
(一个集群 代表的是 拥有同样类似功能的服务器的组合 也就是多个服务器一起提供同样的一种服务给用户)
这是因为 不管是网站也好 一个APP也罢 通常用户流量都很大(越是知名的产品 用户就越多 流量也就越大) 在很大的流量的情况下, 一台服务器就算配置再高 也不可能扛得住的哦 :P .
一个服务器总归有它的承担流量的上限, 这个就叫做负载量, 一个服务器撑不住 自然就需要多个服务器一起来抗住流量
怎么才能"一起抗"? 直接对着服务器喊话 一拥而上? 不可能哦
这就涉及到一个关机的概念 叫做 负载均衡, 所谓的负载均衡 就是通过一种技术手段 把原本一股/一堆请求 按照一定的规则 分摊到多个服务器之上
就有点类似"大禹治水"的经典故事, 一股湍急的洪水 堵是堵不住的 只能同时挖多个小的渠道 把洪水等份的分开 流出去
应该不难理解吧 ^\_^
那么回到咱们的主题上,负载均衡由谁来提供呢? 不言而喻 自然就是 作为反向代理的NGINX啦
因为流量肯定先汇总到挡在前面的NGINX , 然后再往后指派了 对不
用一幅图来表示 负载均衡
![](assets/1593412292-0b59be272b3b5dd32b9a41696b62c7ed.png)
第二点 功能分配问题
反向代理的第二个重要意义 是有效率的把不同种类的请求 指派给不同的服务器 或者集群 来处理
第四单元中 我们讲过NGINX的域名和SERVER{}模块 还没忘吧?
一个WEB请求到达NGINX后,HTTP{} -> 指派 SERVER{} 接手 , 并按照访问的域名的不同 给予不同的"待遇"(响应)
对于一款产品来说 , 就比方说 一款社交软件APP , 从一个傻瓜用户的角度来看 在APP上面作任何操作 都感觉是同样的一种"服务"在背后运作着
但是从一个工程师的角度 我们必须认识到 一个产品对应的背后 往往是一大片且功能不同的集群
举个例子来说, 当用户点击登录的时候 就可能是服务器\[A,B,C\]来帮处理的,当用户看到一个人的头像时 可能就是 服务器\[D,E,F\]来处理的
当用户和一个美女开始聊天时 就可能是服务器\[G,H,I,J,K\]处理的 以此类推 不同种类的请求 很可能就是由不同的服务器/集群 去分别应对
而用户的任何"动作"发起时, 大多数情况下 都是先统一以 HTTP的形式发送过来(这个我们在序章就给大家说过的)
而\[A,B,C\] \[D,E,F\] \[G-K\] 这些不同种类的服务提供者 它们可做不到 "主动" "自发"的去 把属于自己的活儿挑出来 揽过来的哦 没那么智能^\_^
在这种情况下,就必须得有一种服务 ,既能认识到HTTP的请求, 又能按照请求中的不同之处(可能是域名的不同 也可能是URL的不同 也可能是URL的参数不同 等我们讲到HTTP协议的时候 会细讲 先别着急) 来指派给不同的地方来处理
所以说,能提供这种的功能的 很自然就落到了 作为反向代理的NGINX上了
举个简单例子, 用户输入 login.example.com 就分配给处理用户登录的服务器, 用户输入 chat.example.com的时候 就分配给处理用户聊天的服务器
但是这里需要特别说明一点, 所谓的用户输入 很多时候 用户是不知情的哦
什么意思呢? 大米之前在序章就说过,很多互联网的产品 虽然大量使用HTTP协议,也就说会有大量的URL网址的传递,但是它不是一个网站哦, 就好比刚才咱们拿一款社交APP来做例子, 所谓的用户输入login.example.com 其实用户不是在浏览器输入这个的 他也很可能压根就不知道 这里也没有浏览器的概念, 他只是傻瓜式的在APP界面上 点击了"登录按钮", 真正发送出来 login.example.com的并不是"用户自己", 而是"按钮"
第三点 充当安全守护者
这一点比较容易理解
咱们已经明白了 反向代理都是 挡在"真正服务器提供者"的前面的 对不? 而且用户毫不知情
用户输入一个网站域名,解析出一个IP地址的时候,其实就是反向代理的IP地址 (当然了 也有可能是 个VIP地址 这个我们以后再讨论)
这样以来 真正提供服务器的 后端服务器的IP就被保护起来了, 用户并不知道它们的存在
第四点 日志的统一化采集
企业中普遍存在这样一种情况, 就是 因为请求都先到达反向代理这一层, 然后再转交后端
那么 反向代理这一层 可以作为 收集用户访问日志的最好平台
NGINX的日志功能 我们后面会学到
【为了配置反向代理 引入两个新模块 】
前面的理论基础 咱们掌握的差不多了 接下来趁热打铁 货需越来越"干"
如果想让nginx启动反向代理功能, 咱们在之前学习的基础上,需要再引进两个新的模块 才能完成
第一个新模块 upstream{}
这个词表面意思是"上游" 呵呵, 其实它是让nginx实现代理功能的 重要核心模块
(早期的nginx 并不具备这个功能 )
反向代理 就仰仗它了 一会咱们看实例 怎么来用它
第二个新模块 location{}
之前提过一次吧 ,现在就到了必须用到它的时候了
location是干嘛用的呢? 大米跟大家说过 HTTP请求的基本形式就是 URL(通常讲的网址) , location就是专门用来接手和对付它的
这里提前打好预防针 location可以说 是出现在整个nginx中 频率最高的模块 没有之一! (不管到哪个企业中 只要用到nginx 都一样)
但是偏偏这个玩意 又可以说是 nginx学习中 除去后面的代码开发部分以外 最难掌握的 很容易造成身体不适(更不要说 后面又要与其结合的正则表达式)
(但是在咱们当前这个单元 先弄的简单点 我们当前的重点还是 反向代理 , 至于说location的细讲 后面会有大量的篇幅 现在先别紧张 -\_-)
这两个模块 简单的说 第一个用来定义反向代理的"对象"(到底扔给后面的谁谁), 第二个用来应对用户输入的URL(或者说 传过来的URL 你懂的~)
接下来 咱们说一下 这两个模块 在整个"模块化"的nginx中 处于什么位置
咱们现在已学会的 http{} ->包含server{}模块 , 这个没问题了吧
首先 upstream{} 模块 和 location模块 全都一样 也附属于http{}其下,这个也没问题 对吧
再接下来
upstream{}模块 和 server{} 是并列的关系 , 换句话说呢 就是 这两个模块在http{}下 分别开来定义
就像下面这样
http{
upstream {}
server{}
}
然后是location{}模块, location{} 必须定义在 server{}当中才可以,换句话说 location附属于 server{}下
这样说 可能也不是特别好理解, 别着急 接下来 咱们附上nginx配置
\[NGINX反向代理 把配置讲透彻一点\]
我们先多准备出一台服务器来, 用它来作为 "后端"
这台服务上 请大家也配置好 nginx , 并且最少配置上一个主页 并且在这个主页中 写清楚标识好 这是"后端" 服务器
在大米这里 , 目前有两台服务器了, server01 , server02
server01 就是咱们核心配置nginx的地方,也是要马上要配置反向代理(以及负载均衡)的地方
server02 是一台新部署的机器, 在这个上面 大米不但配置了一个新nginx的主页, 另外 还附带配置了一个 "监控后端"
(这个监控后端 不是nginx哦 而是一个也可以响应http请求的 "监控组件" 来自于prometheus普罗米修斯监控, 每次请求它的时候 它都会去检查数据库的各种状态 然后给你返回一堆的结果 在这儿正好充当一个 "动态站点" )
这样以来 ,大米的 sever02这台机器上 就存在了两个不同的WEB应用 , 一个是纯静态nginx页面, 一个是动态监控组件 , 都可以响应http的请求 正好方便我们教学中的反向代理举例子
(有条件的朋友 也可以在 自己的第二台机器上,部署一个新的WEB应用, 例如 PHP 或者 JAVA 都可以,我们后面会讲到PHP的 目前不强求)
这个后端server02准备好了以后, 接下来 咱们开始讲解 怎么配置nginx反向代理 把不同种类的请求 指派给不同的后端"应用"
上截图
![](assets/1593412292-75b4673d9b5bf8c5c32ce4810b1b700f.png)
![](assets/1593412292-8f6226c51f70e9c9f8ac3566825b6164.png)
这两张截图 就包含了我们配置反向代理的 基本内容了
接下来 大米给大家叙述一下 模块的调用流程是怎么走的 按照1 2 3 4 5
1:用户输入了一个 html.example.com
2:网址被DNS解析到了 咱们的反向代理服务器 server01的IP上了
3:server01上面的 nginx下的 HTTP{}先接手
4:HTTP{}接手后 ,把请求中的URL(html.example.com) 提出来, 然后一个一个按顺序 跟SERVER{}进行对比
5:对比到 最后一个SERVER{}的时候, 这个SEVRVER{}中的 server\_name 正好就匹配上 html.example.com (还包括80端口匹配上了), 所以 由这个SERVER{}接手
6:接手后 ,请求继续进入这个SERVER{}内部,该进行下一层的匹配(Location匹配) 也就是URL的匹配了(也就是说 域名匹配完后 进行更细的匹配 看看用户输入的网址后面 有什么讲究)
7:LOCATION{}模块在这里 写的是 location / , 这个斜杠啥意思? 在这儿的意思是说 , 用户输入了网址 html.example.com 这个之后 如果再输入一个/(即便你不输入 "/" 浏览器也自动帮你补齐了) 就匹配上了的意思. (这里不好理解 下面马上就单独 解释这儿是啥意思)
8:Location匹配上了之后,进入location, 发现这里有个 proxy\_pass? 这里的意思是 就是代理转发了哦 ,说的白一点 , 请求匹配到 Location{}里以后,直接就要扔给"后端"服务器了哦
9:接下来 就是决定要扔给谁了 , proxy\_pass [http://html/index.html](http://html/index.html), 这里注意 扔给的是 [http://html/index.html](http://html/index.html) 这个地址, 但是这里有一个特殊的地方, 就是 中间那个"html", 这个可不是个域名哦(咱可根本没定义出 一个叫做html的机器名 ), 而是一个别的地方的引用
10:html这个"引用", 其实是哪里定义的? 我们看 upstream html {} , 看到这里就明白了 对么? 其实请求到了location后, 不是直接扔给"后端"具体的一个机器去了, 而是先扔给谁? 回答:先扔给 upstream html{}模块
11:接下来,upstream html{}模块中, 定义了一个 server server02:8080 , server02 就是咱们的真正的第二台机器的机器名哦 当然这里直接写IP地址也是可以的
所以说 真正把请求扔给"后端"的 是upstream做的事情 ,这就是NGINX的核心代理模块的作用了
如上, 一个完整的反向代理流程 从发起 到 结束 就是按照这个原理走了一圈
接下来 , 咱们返回头 再给大家解释一下 当中第8步 location的匹配, 希望可以很好的理解
location / ????? 这里估计有人开始犯晕了, 所以 咱们多说几句 单独解释一下
平时你输入网址 比如 www.nginx.org, 立刻就打开nginx官网了对不? 但是 实际上 URL网址 完整的格式 是类似如下的
[http://www.nginx.org:80/](http://www.nginx.org/)
这才是完整的 nginx的官网首页的 URL地址写法
你注意到了没有,其实完整的URL 在端口80最后 还有一个 / , 这个/其实是一个重要的分水岭
在这个结尾的/ 之前的部分, 是协议+域名+端口, /之后的部分 我们称作 WEB路径资源(往后 我们还会讲到 更后面的参数部分 )
/ 之后 接上不同的"路径资源",就会访问到 这个网站其他的页面 或者是 应用了 (当然了 前提是 你输入的路径 是有效的 并且人家的WEB SERVER中 也配置了的 才可以 )
例如 如下这样 就可以访问到 nginx官网的不同 页面资源了
[http://nginx.org/en/download.html](http://nginx.org/en/download.html)
[http://nginx.org/en/docs/](http://nginx.org/en/docs/)
(假如你胡乱输入一个 / 之后的路径, 比如 nginx.org/hahaha 会咋样? 必然会页面访问不存在了哦,还会给你返回一个错误代码 因为人家网站根本就没定义出这个 路径资源, 这就是我们后面会讲到的 404 返回)
到这里 大家会有一个疑问了, 那我为啥 只输入 www.nginx.org 就能打开首页了? 最后那个/ 和前面的 http:// 我也没输入啊
这就是浏览器存在的意义了, 所有的浏览器 都支持 基本的URL补齐功能, 所有很多地方 不需要你输入 也能带上 但是 并不表示 它就消失不见了
明白了这个之后, 再来看location / 到底咋回事, 咱们上面说了, 网址最后 必须存在一个 / 结尾, 不管你自己输入不输入 它肯定都存在
也就说 localtion / 代表, 如果网址中 存在 / 结尾, 接下来就匹配上了
然而 网址最后 铁定是会有一个/结尾的 这是雷打不动的啊 所以 这里实际上 location / 就变成了一种 "默认匹配"了, 因为你不管怎么着 它都一定会以/结尾的 也一定会被这个location 匹配上, 现在明白了一点了吧? ^\_^
(如果还是有点犯晕 也不用着急, 后面等咱们专门讲location的时候 有大量的实例出现 会越来越明白的)
再举一个例子
你可以找一台机器 用yum install nginx 就随便装上它 然后你 看看默认情况下的 nginx.conf中
一定就会存在 一个 location / {} 了, 意思就是说 有了这一项配置, 即便你不配更复杂的location 规则 或者根本不懂location, nginx也起码能有一个 默认的location 可以处理你的基本 URL请求 OK?
明白了如上这一流程后, 咱们继续把这个配置说完
咱们的反向代理中 一共配置了 两个upstream{}模块 如下
upstream prometheus {
server server02:9104;
}
upstream html {
server server02:8080;
}
第一个 负责扔给 server02的9104端口 , 也就是 server02上面的 prometheus监控插件 所处的位置
第二个 负责扔给 server02的8080端口, 也就是server02上面的 nginx静态页面 所处在的位置
相对应的 server{}+location{}的组合 也有两组
分别是如下
```plain
server {
listen 80;
server_name prometheus.example.com;
location / {
proxy_pass http://prometheus/metrics;
}
}
server {
listen 80;
server_name html.example.com;
location / {
proxy_pass http://html/index.html;
}
}
```
这里走的还是 按照server\_name来区分, 输入prometheus.example.com就走到 监控这个应用, 输入html.example.com 就走到 静态页面 这个应用
如下是 结果图, 两个域名 对应到各自的应用
(监控的这个返回结果 看不懂没关系 ^\_^ 属于prometheus监控范畴 感兴趣的 可以自己学, 在这里 只不过就是充数 作为一个 动态站点应用 被反向代理转发过来)
![](assets/1593412292-78255ab43a20b1281752edb2a864e460.png)
\[遗留问题解答 和下篇预告\]
关于本篇的内容 大米觉得 说到这里 很可能大家会有这么两个疑问在脑子里 看看谁有哈 ^\_^
第一个可能遗留问题:
大米老师: 那个proxy\_pass 是启动和调用 转发的 ,这个我明白了, 不过 奇怪的是 为啥非要在 location{}中才能用它
为啥不能在 server{}中 就调用proxy\_pass 呢, 也就是说 server{}匹配了域名之后 , 直接就调用proxy\_pass给后端就行了 为啥还非要再调用个 location{}?
就好比 之前的篇幅中 server{}匹配之后,直接就可以 写上root 给予回应了嘛 不是也没非要用到location吗
就像如下
server{
listen 80;
server\_name xxxx;
root /var/www/xxxxxx ;
}
我的回答是: 就像第三章节中 大米跟大家说过的一样 nginx的模块下会有配置项, 而有的配置项是 某些模块所独有的, 有些配置项 是可以共享的
比如就拿这个 root 网站资源根目录 来说, 它即可以 在server{}中直接调用, 也可以在 server{}中的location{}中再调用
如下这样
server{
...
...
root xxx;
}
或者
server{
xxx
location{
root xxxx;
}
}
但是对于proxy\_pass 这个配置项, 就只能在location{}中调用, 在server{}中就不行了 咱们可以很简单就测试出来
![](assets/1593412292-6c72abc74e9bcdc40d9b33a954517ea3.png)
看到了没? 直接报错 proxy\_pass 不允许在 server{}中直接调用, 非得嵌入到 location中才可以
这也就解释了为啥 咱们反向代理这一篇中, 两个必须使用的模块 其中之一 就一定要有 location{}
第二个可能遗留问题:
大米老师: nginx的反向代理和负载均衡 这两个感觉容易有点搞混了 是包含的关系 还是并列的关系 不太好区分啊
(首先,负载均衡的配置 在上篇中 我们还没讲, 会放入下篇中细讲)
回答这个问题是这样的
其实反向代理的概念 比负载均衡要大
咱们前面也说过了, 反向代理其中的一项重要的功能 就是可以提供负载均衡 所以 可以理解为 一种包含的关系
我设想这样的两种场景
服务器A(NGINX) -> 服务器B(应用)
服务器A(NGINX) -> 服务器B(应用)
\-> 服务器C(应用)
这两种NGINX配置,第一个是 只转发代理给1个后端, 第二个是转发代理给两个后端
这两种都必须要用到 upstream & proxy\_pass, 所以 两种都属于 反向代理 这是必然的
只不过第二种 因为同时发给两个后端, 就必须还得启用 反向代理中的 负载均衡功能 才能完成 (启动 upstream中 load-balancing的功能 )
这样说 就比较好理解了
最后, 咱们说一下 下篇的预告
下篇中 , 我们还是围绕 主题 反向代理和负载均衡
我们会针对反向代理 讲解一些更高难度的应用配置项,和匹配规则 , 另外 负载均衡 我们也会把原理深入 并加入一些算法和实验
好了 休息一下眼睛吧 ^\_^
@@ -0,0 +1,615 @@
## 6.NGINX做反向代理和负载均衡,企业中必用(下篇)
【我们先说 上篇中遗留下来的 负载均衡功能】
负载均衡是做什么用的 上一篇中我们给大家解释过了 这里就不再啰嗦
我们现在当务之急 是先把nginx的负载均衡给实现出来 稍后 我们再讨论一些深入的问题
nginx实现负载均衡的话 在咱们之前讲的反向代理的基础上 就变得非常的简单了
就是在 upstream{}模块中 继续陆续 添加多个后端"节点"(节点指的就是 后端的服务器)即可
请看下面的截图
```plain
upstream prometheus {
server server02:9104;
server server03:9104;
...
...
}
upstream html {
ip_hash;
server server02:8080;
server server03:8080;
...
...
}
```
就像这个样子就好了,原本之前upstream只反向代理给一台服务器,我们现在多加几台后 就变成了 反向代理给多台服务器了 这就变成了负载均衡了^\_^
是不是很简单呢?
简单是简单,但是在这里需要注意 , 凡是你添加到upstream{}里面的 后端机器(server02/03/04....),请你务必先手动确认 每一台要被添加的机器 是确实可用的,并且拥有一致的功能 别给搞错了哦
检查语法没有问题后,接下来 咱们准备开始做测试了 看看这个负载均衡 是不是真的像咱们预期的一样 把同一股请求 分散到多个机器上去了
但是这里有一个问题了,咱通过什么才能看到 一台服务器上 接受到了请求呢?
这就要临时引入一个新的知识点 也就是nginx的日志功能 如果不懂得怎么查看日志 往后的很多工作都无法进行了
【测试负载均衡功能前的准备工作,并介绍nginx日志功能】
接下来 咱们的实验 需要大家一起 做这么几个准备工作
1) 一共需要最少三台虚拟机
2) 每一台虚拟机上 都请安装好nginx
3) 后两台作为 负载均衡后端的NGINX 请分别建立相同位置的html主页 来作为结果展示(可加入不同标识在页面中 以方便咱们辨认)
4) 需要一个简单的压测shell脚本 (一会就跟大家说 怎么来写)
5) 确认每台nginx上 都开启了 日志功能 (稍后就会讲到)
第一项 第二项 第三项 没什么太多好说的了 大家已会 请自行准备
第四项:
最简单的压测shell脚本 直接复制给大家 很简单 就是使用while死循环 + curl 即可
其实就是 无限次的不停发送 HTTP请求了
```plain
while true
do
curl html.example.com
done
```
curl后面指向的是 你的最前面的负载均衡机器中 定义的server\_name , 自行更换即可
(shell脚本无论是在本专栏中,还是在Linux的学习中 都是必须必须掌握的 请私下多多练习)
第五项:nginx日志功能的开启
这里到了一个重头戏了 一起跟住 学好!~
nginx记录实时日志的功能 其实也是由一个模块来完成的
(nginx处处是模块 这里又是一个很好的体现)
这个模块名称叫做 ngx\_http\_log\_module
这个模块 其实是作为 nginx HTTP模块当中的一个 "下属模块" 存在的, 我们在这个小节中 先不管模块的问题 先学好其使用方法
nginx日志虽然是作为一个"模块"来调用的, 但是 它却不像server{} , localtion{} 那样 必须带上一个花括号才能调用
nginx日志的开启 只需要以配置项的方式 定义最少两项 就可以使用了
这两项配置项分别是 log\_format(日志格式) 和 access\_log(日志调用)
```plain
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
```
先看第一项 log_format ,看着有点长 不太好懂 对不?^_^
其实它是由三个部分组成的, 就是 log\_format + \[日志名称\] + \[日志内容变量\]
log\_format总体来说 就是定义一个日志格式,说白了 这份日志当中 你希望在里面会出现记录下 什么样的内容
定义好格式和内容之后, 最后给它起一个"名字" 叫做 main
(日志内容变量的部分比较复杂 且内容较多,我回头单独学习nginx日志功能的时候 再详细讲解 目前先使用默认自带的)
这个main名字是干啥用的? 其实它非常关键
如果你仅仅是 制定了日志的格式 日志并不会立刻出现在你眼前的,它必须要再一次被"调用起来" 才能生效的哦
调用是谁调用的? 就是第二项的 access\_log
access\_log /var/log/nginx/access.log main;
第二项的配置 也是由三个部分组成,access\_log + \[日志产生的位置\] + 调用名称
这里注意 最后一项的 "main" 它其实就是 调用了 你之前刚刚定义好的 日志格式的"名字"了
这两项配置内容 由于都是http{}的下属,所以 直接粘贴在 http{}下即可
例如这样:
```plain
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
```
这两项定义好之后,当你的nginx再被访问到的时候,就会在 /var/log/nginx/ 目录下 产生名字为 access.log的日志了
就像下面这样
![](assets/1593412322-b19dc348afcfafd68fddcb0b8b03b129.png)
顺带一提的是,当前最新版本的nginx的配置文件中,即便你不配置上日志的配置项
(你可以试试把 nginx.conf中自带的 log\_format , access\_log 都删掉或者屏蔽掉)
nginx默认也会产生一份日志的,只不过 都是采用默认的一些日志格式 和 位置
其实你可以 试试找一下 /var/log/nginx/ 下,不管你配置没配置 nginx日志 , 这里都会已经有一份默认的日志一直在 产生中了
【万事俱备了 开始测试负载均衡吧】
大米的三台机器, 第一台 server01 依然作为反向代理和负载均衡, 同时 也是作为入口的机器,请求先到达这里 然后再分配到后面的两台机器
第二台 和 第三台 server02 & server03 都做好了一个简单的静态html页面,访问日志 存放在默认的 /var/log/nginx/access.log
(这里大米偷懒了,就如上个小节末尾跟大家说的,新版nginx 即便你日志功能不配置 甚至都屏蔽了 nginx也可以正常产生默认格式和位置的日志的 其实 server02 server03的nginx.conf配置中 我都没有写任何的日志配置,就用默认自带的就可以了 因为当前 我们只需要清楚的看到日志的产生 即可 并不在意 日志的具体格式,这个到后面的 专门的日志章节中 我们再细讲 )
server01的配置文件如下
```plain
http {
include mime.types;
default_type application/octet-stream;
log_format prometheus_log '$remote_addr $time_local' ;
log_format html_log '$remote_addr $time_local' ;
upstream prometheus {
server server02:9104;
server server03:9104;
}
upstream html {
server server02:8080;
server server03:8080;
}
server {
listen 80;
server_name "";
root html;
}
server {
listen 80;
server_name prometheus.example.com;
location / {
proxy_pass http://prometheus/metrics;
access_log /var/log/nginx/access_prometheus.log prometheus_log;
}
}
server {
listen 80;
server_name html.example.com;
location / {
proxy_pass http://html/index.html;
access_log /var/log/nginx/access_html.log html_log;
}
}
}
```
咱们来拆开解释一下上面配置是什么意思
先看最上面的这两行
```plain
log_format prometheus_log '$remote_addr $time_local' ;
log_format html_log '$remote_addr $time_local' ;
```
日志配置中的 log\_format 我们紧挨着 http 下给写上
有人可能会问 为什么log\_format定义了两个呢?
是因为 大米的站点中 一共存在两个不同的WEB应用
一个是 静态html主页, 另一个是监控动态页面 这个咱们之前讲过的 没忘吧^\_^?
大米这里是希望, 两个不同的应用 在server01上 都对应有不同日志的记录
这两项日志 分别起名为 prometheus\_log 和 html\_log (后面即将调用这里的名字 很重要哦)
这里又有人会问了: access\_log跑到哪里去了? log\_format下没看到啊
其实你继续往下看 access\_log只不过换了个地方 是在如下这里定义的
```plain
location / {
proxy_pass http://html/index.html;
access_log /var/log/nginx/access_html.log html_log;
}
```
在这里就可以看到了 ,其实access\_log 位置是不固定的,而log\_format位置是固定的
log\_format只能定义在 http{}的"外围之中", 也就是说,不能定义在 http{}当中的任何其他的模块中{} 了 ,它相当于是一个全局的"设置"
而access\_log呢? 它位置就不固定了,接下来给大家解释一下(这里理解有一定难度)
access\_log 本身的功能 是调用启动日志记录,换句话说就是 啥时候 或者 啥条件下 该启动日志记录了 就写上它
如果把access\_log 放在和 log\_format一样的位置上 如下所示
```plain
http{
log_format xxxx xxxx
access_log xxxx xxx
server{}
}
```
这样的配置 就属于 全局配置了,后面不管是哪个server{} 或者 location{}起作用后 都统一使用一样的日志配置
如果把access\_log 放在 server{}中 或者 location{}中呢?
例如下面这样
```ruby
http{
log_format def
log_format dhk
server{access_log def }
server{
location{access_log dhk}
}
}
```
如上这样的配置 日志只有在满足某个server{}匹配上,或者 location{}匹配上的情况下,日志才被调用起来 明白吗?
这里还有一个有意思的规则 我们需要给大家先提出来
那就是 nginx的配置本身遵从一种 "优先细 忽略粗" 的关系
这啥意思啊 ~~~~???
我们用如下这一段配置来举个例子
```plain
server {
listen 80;
server_name html.example.com;
access_log /var/log/nginx/access_html2.log html_log;
location / {
proxy_pass http://html/index.html;
# access_log /var/log/nginx/access_html.log html_log;
}
}
```
上面这一段配置中,你可以看到, server{}下 定义了一个access\_log , 存放名称是 access\_html2.log
然而 server{}下的 location{}中 又定义了一个 access\_log 存放名称是 access\_html.log
这里起到的效果是, 如果location{}匹配上了,那么它access\_log就会启动,并且还忽略掉 server{}下面的 access\_log设置
我们在server01上 就按照这个配置 来跑一下 压测脚本吧
压测的是 html.example.com 这个域名 (目前先不管负载均衡 ,我们只看server01上本身产生的nginx日志)
\[root@server01 conf\]# while true; do curl html.example.com; done
(其实大米这里是 在server01上 添加了域名绑定 然后自己压自己 这样比较方便 比从公网外去压 速度快 )
压测之后,日志如下产生
![](assets/1593412322-a4f3399c95328e7ad70c181ee8910149.png)
看到了吗?
location{}当中的access\_log生效了于是诞生 access\_html.log,于是乎 就忽略了 server{}下的 access\_log, 不会产生access\_html2.log的内容
(容量是 0 说明没有日志写进去 只是有个空文件放在那里,空的.log日志文件,是在执行 nginx -s reload后 就立刻产生)
好了 言归正传
我们sever01 这样配置的目的, 就是希望
当请求访问的是 html.example.com的域名时, 日志记录到
access\_log /var/log/nginx/access\_html.log html\_log;
当请求访问的是 prometheus.example.com的域名时,日志记录到
access\_log /var/log/nginx/access\_prometheus.log prometheus\_log;
这样就非常清楚明白了
server01先说到这儿, 接下来是 server02 server03的配置
server02 03 的静态页面应用 通过nginx提供
配置如下即可
```plain
server {
listen 8080;
server_name localhost;
root /var/www/;
location / {
index index.php index.html index.htm;
}
}
```
配置成这样就可以了,很简单的,因为 后端nginx就只要能提供个 主页即可 不需要额外配置了
提供让两台后端nginx都监听在 8080端口(其他端口也可以),配置好一个主页地址即可
到这里 我们就做好准备了 接下来就是开始测试 负载均衡功能了
咱们使用简单的压测脚本 对html.example.com 先开始
while true; do curl html.example.com; done
![](assets/1593412322-19b935ada25932f121fd7e2ef84985ee.png)
上图就是 对静态页面负载均衡 html.example.com的循环访问的返回结果了
由于server02 server03 的主页 分别加入了区分标识 所以 很容易就看到 请求有时被分配到02 有时被分配到03
到这里 爱思考的学员 就会有疑问了: 负载均衡负责把请求 分给后端的多个机器时,是否会遵循一个什么规则么? 总不会是 想给谁就给谁吧?^\_^
那么接下来 咱们就来看一下 nginx负载均衡 所提供的"算法"
nginx分配负载均衡的请求 一定会按照某一种指定的 或者默认的 "算法" 来进行
在这里 我们先把 这几种算法 都罗列出来看看先
(如下这一段 取自其他出处)
一、Nginx负载均衡算法
1、轮询(默认)
每个请求按时间顺序逐一分配到不同的后端服务,如果后端某台服务器死机,自动剔除故障系统,使用户访问不受影响。
2、weight(轮询权值)
weight的值越大分配到的访问概率越高,主要用于后端每台服务器性能不均衡的情况下。或者仅仅为在主从的情况下设置不同的权值,达到合理有效的地利用主机资源。
3、ip\_hash
每个请求按访问IP的哈希结果分配,使来自同一个IP的访客固定访问一台后端服务器,并且可以有效解决动态网页存在的session共享问题。
4、fair
比 weight、ip\_hash更加智能的负载均衡算法,fair算法可以根据页面大小和加载时间长短智能地进行负载均衡,也就是根据后端服务器的响应时间 来分配请求,响应时间短的优先分配。Nginx本身不支持fair,如果需要这种调度算法,则必须安装upstream\_fair模块。
5、url\_hash
按访问的URL的哈希结果来分配请求,使每个URL定向到一台后端服务器,可以进一步提高后端缓存服务器的效率。Nginx本身不支持url\_hash,如果需要这种调度算法,则必须安装Nginx的hash软件包。
6、least\_conn
Web请求会被转发到连接数最少的服务器上。在这种场景下,把请求转发给连接数较少的后端,能够达到更好的负载均衡效果,这就是least\_conn算法。
由于篇幅的关系 ,咱们只通过实验 给大家讲解 其中在企业中 使用最普遍的三种算法
也就是 轮询 加权轮询 和 ip\_hash
(其实网上介绍各种nginx负载均衡算法的很多,咱们在这里主要希望大家通过 脚本和实验来掌握的更加清楚明白)
我们先介绍 nginx的默认LB(load-balancing 负载均衡简称) 算法, 那就是轮询
轮询这种算法 很简单易懂 说白了就是 先扔给第一个 再扔给第二个 再扔给第三个... 除非有一台后端"挂了" 不然就是周而复始的 按这样的方式来分配请求
咱们上面配置负载均衡 upstream{}的时候 貌似没有指定什么算法不是吗? 其实什么都不指定的话 默认就是 轮询算法了哦 ^\_^
光说不练是可耻的! 咱们接下来 用脚本和实验 精确的看看 默认轮询 具体怎么分配请求的
我们首先把之前的 简单压测脚本 做一下改动
改成这样子
```plain
for i in `seq 1 20`
do
curl html.example.com
sleep 0.5
done
```
这段脚本的意思是 循环一共20次访问入口html.example.com(seq用法 可以自行查阅 )
每次间隔0.5秒
之后 我们通过在 server02 server03上 分别执行 tail -f /var/log/nginx/access.log 就可以看到 如下的结果图了
(tail -f 可以实时查看一个文件末尾内容的增加 是使用Linux中 必须掌握的方法)
![](assets/1593412322-43638cceb468102d6ddeff7429902c34.png)
由于我们加入了 时间间隔0.5秒执行, 就可以很清楚的看到 ,一次执行 先到了server02, 下一次执行就到了 server03 周而复始 平均分配给后面的两台
图右边显示最终,每台后端服务器 都收到了一共 10条日志(也就是被访问到了10次)
这就是默认的 轮询规则了 很简单
接下来 咱们来看一下 轮询加权重 是怎么样的
我们先把nginx的 配置改成如下这样
```plain
upstream html {
server server02:8080 weight=10;
server server03:8080 weight=20;
}
```
在每一行的后面 加入 weight=xx ,这个就代表轮询的权重值了
权重值越高 越会更多的被分配请求 而 相反的则分配的越少
这个也很好理解吧
咱们接下来通过 实验来看一下 加入权重之后的 轮询 是什么样的结果吧 ^\_^
![](assets/1593412322-9904fb708a7710bf28e1accadaf6181b.png)
上图中 我们可以看到, 同样的20次循环测试, 右上角的server02 由于权重值是10 , 只被分配到了 6次请求, 而右下角的server03 由于权重是20 ,被分配到了14次请求
所以,通过实验证明,权重值越高 轮询被分配的请求越多,并且 还是按照权重值的比例 来进行分配
(server03 的权重 正好是 server02的两倍 , 分到的请求 也正好是两倍了)
如上就是 加入权重值的轮询规则, 这种LB的算法 在企业中使用的最为普及(尤其是 中小型企业) 因为可以根据权重值 来灵活的调整 给后端分配请求的负载量
接下来 咱们再看 ip\_hash的 LB算法, 也就是 基于客户端IP哈希值 来进行请求分配
这种算法相对特殊一些, 大米来解释一下怎么回事
当每一个客户端(用户)在发送访问过来的时候,都会带有客户端的IP地址,这种又叫做 来源IP地址
比如 在我们nginx的 access.log中 默认就会记录这一地址
```coffeescript
[root@server02 ~]# cat /var/log/nginx/access.log | tail
192.168.1.225 - - [16/Dec/2018:17:52:56 +0800] "GET /index.html HTTP/1.0" 200 83 "-" "curl/7.24.0 (x86_64-unknown-linux-gnu) libcurl/7.24.0 OpenSSL/1.0.1e zlib/1.2.3 libidn/1.18"
192.168.1.225 - - [16/Dec/2018:17:52:58 +0800] "GET /index.html HTTP/1.0" 200 83 "-" "curl/7.24.0 (x86_64-unknown-linux-gnu) libcurl/7.24.0 OpenSSL/1.0.1e zlib/1.2.3 libidn/1.18"
125.64.94.201 - - [16/Dec/2018:17:53:39 +0800] "GET / HTTP/1.0" 404 169 "-" "-"
192.168.1.225 - - [16/Dec/2018:17:58:16 +0800] "GET /index.html HTTP/1.0" 200 83 "-" "curl/7.24.0 (x86_64-unknown-linux-gnu) libcurl/7.24.0 OpenSSL/1.0.1e zlib/1.2.3 libidn/1.18"
192.168.1.225 - - [16/Dec/2018:17:58:17 +0800] "GET /index.html HTTP/1.0" 200 83 "-" "curl/7.24.0 (x86_64-unknown-linux-gnu) libcurl/7.24.0 OpenSSL/1.0.1e zlib/1.2.3 libidn/1.18"
192.168.1.225 - - [16/Dec/2018:17:58:19 +0800] "GET /index.html HTTP/1.0" 200 83 "-" "curl/7.24.0 (x86_64-unknown-linux-gnu) libcurl/7.24.0 OpenSSL/1.0.1e zlib/1.2.3 libidn/1.18"
```
注意到 日志当中 左边第一列的数据了吗? 192.168.xx , 125.64.xx 。。等等 ,这个就是记录下来的来源IP地址
(不过 这个来源地址还有很多说头儿, 有时候获取起来并不这么简单, 后面 专门讲日志的时候,会对日志格式内容 作更详细的讲解)
总而言之,ip\_hash的算法 就是基于这个IP地址 用它作一个数学运算 最终得到一个结果值, 不同的来源客户端 在短时间内 得到的这个结果值是不一样的
短时间内 相同的结果值的请求 就会被分配到同一台后端机器上 , 这就是ip\_hash算法
这里有的朋友会问了: 这种算法的意图何在呢? 为什么同一客户端的请求 要分到同一个后端服务器上?
大米的回答: 主要是为了 会话保持 又叫做session保持, 这个理解起来难度比较大 我们放在下一个小节中再解释
接下来 做个实验吧一起
请把nginx配置 改成如下这样
```plain
upstream html {
ip_hash;
server server02:8080;
server server05:8080;
}
```
就在第一行 新加入了 ip\_hash; 这样即可
接下来运行咱们的压测脚本
![](assets/1593412322-6f052bf1238e8834e5f729d4757cffe8.png)
从上面的结果来看,所有的请求 都被发送到server02 一台去了 , server03一条日志都没有
因为 咱们的循环测试 都是来自于咱们自己同一个客户端的呀,所以来源ip相同,自然每次请求 都计算并分配到同一台后端去了
如上 我们就通过实验 验证了nginx使用较为普遍的三种算法
另外 在upstream{}的负载均衡配置项中 还可以添加一些 其他额外的实用配置的
例如:末尾标记down之后,server02这台机器 就被负载均衡"抛弃了" 不会再有任何请求发送上去,而全都跑到server03去了
```plain
upstream html {
ip_hash;
server server02:8080 down;
server server03:8080;
}
```
再例如:末尾标记 backup之后,这台机器就成为一台 备用机器了,意思是说 只要其他的机器都还可用的情况下,这台机器就不会接受到任何请求
upstream html {
server server02:8080 weight=10 backup;
server server03:8080 weight=20;
}
大家可以自行作实验 来证明其功效
# 【session会话保持的理解】
我们在上一个小节中 提到了 ip\_hash这一种LB的算法对不? 那么在本小节我们来解释一下 它存在的意义吧
举个生活中遇到的例子
我们平时都有在各类网站上登录用户账号的时候吧,是不是只需要登录一次 然后无论之后在这个站点任何地方 都是可以一直保留着登录状态了对不?
另外,我们平时也有大量"网购" 或者"订餐"的时候 对不? 这种都会遇到一种 叫做"加入购物车" 的动作的对不?
你在挑选货物的时候,通常都是 先慢慢浏览 当看中了一款物品之后,就先把它添加进入购物车 然后继续挑选其他的,在最后结账的时候 购物车一直都保留着你全部挑选的记录的
这种带有记忆功能的 HTTP请求 就涉及到我们所说的 session会话保持功能了
说的简单一点,就是对于同一个用户来说,当你执行登录 或者挑选货物 之后,你在"后端"某一台服务器上 就保留一份临时数据 记录着你的"状态"
当你再发起下一次行动的时候(可以理解为 再次发起下一次HTTP请求的时候),负载均衡会把你的请求 分配到上一次所在的后端服务器上,这样你之前临时记录的数据就找到了
不然的话,如果负载均衡把你的下一次请求 分配到了另一台 没有你的状态记录的机器上,你的状态就丢了
(登录失效了,又要重新登录; 购物车被清空了,之前选购的东西都丢了。。。)
说到这里 肯定会有很多人有这样的一种疑问: 如果这么说的话,那岂不是 负载均衡算法 就只能使用ip\_hash这种方式才可以了,轮询或者最小链接等等其他算法就没有任何用武之地了吗?
大米给大家的解释如下:
在早期的时候某些情况下 确实是这个样子,这个跟后端的程序代码有着密切的联系
比如:一个网购站点采用PHP编写,并且运行在十台后端服务器上
每台服务器的PHP程序代码,都会把当前已登录的用户 临时记录在自己这台服务器之上,而其他的9台机器上是没有的
这种情况下,就必须依赖 类似ip\_hash这种会话保持功能的负载均衡 把同一用户发送到同一台后端PHP服务器上 才可以正常保持会话
但是 随着集群技术的不断推进,代码在不断改进,各种缓存技术也在不断出现
现如今更多的动态站点的程序 往往都不会出现这种 临时数据不共享的情况
比如:当一个用户登录之后,PHP的程序会把 这个用户的登录状态 记录在另外一个缓存集中(比如redis 或者 memcache , 也有可能记录在 mysql数据库),或者记录在用户客户端的Cookie中
而PHP程序(不一定是PHP了)由于不会再在本地存放任何临时且不共享的数据,这样以来 ,十台后端服务器上的程序 都变成了一种 "无状态" 的情况,也就是说 用户每次请求 不管分配到哪一台后端机器 都无所谓了,反正会话保持的数据 可以从别的地方找过来
这样 就不再非要 依靠负载均衡来实现 会话保持功能了
我想通过这一段的解释,大家应该就比较明白了吧 ^\_^
# 【负载均衡的理解】
负载均衡功能目前在企业的集群中 已经是无处不在了
因为 随着互联网爆炸的发展,绝对没有哪个企业只用一台服务器 就能搞得定的 必须有分流
然而负载均衡本身是一个通用技术,并不是只有nginx才可以实现的哦
总体来说呢 负载均衡技术 可以分为 软件和硬件两个大类
所谓的 软件负载均衡 就是在一台服务器上安装某类支持反向代理+负载均衡的软件 来实现 (例如nginx)
然而 硬件负载均衡 就是使用单独的物理设备来实现负载均衡 比如F5, netscaler 等等 这种负载均衡独立设备 性能更加强大 同时"价格也更加强大"
一般在中小企业 我们不太容易能有机会 见到使用到"硬件负载均衡",所以 咱们更多还是以学习 软件负载均衡为主
除此之外 负载均衡 还有另外的一种分类方式,叫做 四层和七层负载均衡
四层和七层是什么鬼???
这个其实是 相对于 著名的OSI七层模型来说的哦
(我们后面的篇幅 会考虑加入 学习TCP网络知识的章节,因为如果对网络不够了解,没办法更好的深入学习 运行在互联网上的nginxWEB)
七层模型不是一两句话能说清楚的 ,需要大量的篇幅,我们在这里 先简单的解释一下吧
所谓的 四层负载均衡,就是工作在比较底层的一种负载均衡方式,它只认识 IP地址和端口,别的就都不知道了
比如 LVS负载均衡 就是知名度非常高的一款,不过它的配置方法非常的复杂
四层负载均衡理论上说 一般效率更高 而且支持的后端的范围更广
但是灵活性不强 只是为了分配请求而分配 并不能做出一些智能化的判断
(DNS解析 其实也可以实现负载均衡的哦,比如同一个域名可以解析到多个不同的IP地址,而且还是以轮询的方式,第一次解析到第一个,第二次解析到第二个 这样就间接实现了 四层的负载均衡)
接下来说 七层负载均衡
所谓的七层,简单的说 就是应用层了,属于比较高层比较智能的负载均衡
就是它主要是基于HTTP协议的负载均衡
也就是说 被他分配的请求,只能是HTTP协议的请求才可以 ,比如我们的nginx
七层负载均衡 由于是工作在比较高的应用层上,所以能加入更多智能的判断 比如URL参数的判定等等 ,只是四层负载均衡所不具备的
七层LB的执行效率 理论上是低于四层的,但是由于其高灵活性 和 易于配置的优点,近些年正在不断取代四层 而成为主流
(之前跟大家说过的,现阶段 大多数的互联网应用的"前端" 接受的都是HTTP的请求,所以很自然七层LB就使用的越来越多)
这么说可能不太容易理解,咱们举个例子吧
假设我们现在 有三台服务器,上面都跑的是 nginx同样的静态页面
由于nginx作为应用后端,本身都是基于HTTP请求的响应, 那么如果要实现三台的负载均衡,我们前面加入一个七层LB即可 (比如 nginx upstream)
假设我们现在 有三台服务器,上面跑的都是 mysql数据库
数据库的请求 可不是HTTP请求了哦, 这种时候 负载均衡 就需要使用四层LB
因为四层LB就是针对 IP和端口,所以它可以不管你应用是什么类型什么协议,只要提供了IP和端口 就可以实现分发了
这样说 就比较清楚了吧
另外 顺带一提的是,新版的nginx 在安装相应的插件后,也可以支持四层的负载均衡哦
到这里 我们的反向代理和负载均衡 就说这么多啦
@@ -0,0 +1,339 @@
## 7.认识Nginx中的location和正则表达式,一起啃这块硬骨头(上篇)
【初识location阶段】
Nginx的location,我们在上一个篇幅中 跟大家提过,确实很不好理解 是不是还是心有余悸呢 (会者不难 不用太担心)
记不记得 我们之前讲过,nginx处理一个请求的时候,都是按用户输入的URL为基准,然后从左至右的来处理的
如果拆开来看一个URL 是这样的
([http://nginx.org:80/static/](http://nginx.org/static/))
\-> http:// -> nginx.org -> :80 -> /static/
URL按照顺序被拆分开来后,相应的 nginx也会调用不同的模块和子模块 来进行处理
大体按照如下这样的顺序
http{}模块 -> server{}模块 -> location模块
下面咱们 通过之前的一段配置来一起看一下 会更明白一些
server {
listen 80;
server\_name html.example.com;
access\_log /var/log/nginx/access\_html2.log html\_log;
```plain
location / {
proxy_pass http://html/index.html;
access_log /var/log/nginx/access_html.log html_log;
}
}
```
上面这段配置中,用户URL输入的 域名和端口 被server{}模块自身就匹配并处理了
而域名和端口匹配上之后,接下来 就是以一个 最右边结尾的 '/' 作为分隔符,再往右的部分 我们称作 "路径"
那么 这个"路径"的匹配 ,就只能交给谁来做呢? 自然就是location{}啦 :)
location的作用 就是用来判断 用户在URL中 输入的"路径"的不同,然后再给导向到不同页面或者位置
(一个网站 总不能仅仅存在一个首页吧,总还得有其他的分页面吧,不然谁还会去浏览呢? 所以 就是有了路径区分页面,而相对的也就有了 处理路径的 location )
接下来 我们先把nginx配置成如下这个样子
```plain
server {
listen 80;
server_name dami01.example.com;
location / {
return 201;
}
location = /nginx {
return 202;
}
location /nginx {
return 203;
}
location ~ /Nginx {
return 204;
}
location ~* /nginx {
return 205;
}
location ~ \.jpg$ {
return 206;
}
location ~ /login/ {
root /var/www/html/vhost/dami01.example.com/;
}
```
}
先简单解释一下 上面这一段的配置文件
可以看到server下 有很多个location并列的放在一起 每一个location表示一种匹配的方法
另外,location中 我们调用了 return 2xx, 这个是代表 一旦匹配上了 就给予不同的返回值
(不同的返回值的目的 就为了让我们在测试的时候 一目了然 知道自己的输入被哪一项location匹配上了 ok? )
那么location到底是通过什么方式 来匹配用户的输入呢?
简单的说 就是location的写法 分成三个部分
: location关键字 + 匹配符号 + 路径字段
例如: location + = + /nginx
而理解的难点,在于中间的 匹配符号这里 ,因为说实在的 这里只能靠死记硬背 并没有什么规则可循
我们接下来 还是直接通过 例子 和 实验 来学习吧
首先 咱们看第一项
```plain
location / {
return 201;
}
```
这一项 代表默认匹配 ,这样的写法就是说 如果其他的location全都匹配不上的时候,就回来找这一项来匹配
所以说 这个默认匹配的优先级是最低的 ,但分后面有一项能匹配上 也不会回来找它 ,除非是乱输入
接下来 我们就来试一试
用如下的方法 curl -I 查看返回码的方式来进行测试
\[root@server01 ~\]# curl -I [http://dami01.example.com/sdfsdfsdfsdfsdf](http://dami01.example.com/sdfsdfsdfsdfsdf)
HTTP/1.1 201 Created
Server: nginx/1.14.1
Date: Sat, 05 Jan 2019 04:57:30 GMT
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
\[root@server01 ~\]# curl -I [http://dami01.example.com/hahaha](http://dami01.example.com/hahaha)
HTTP/1.1 201 Created
Server: nginx/1.14.1
Date: Sat, 05 Jan 2019 04:57:34 GMT
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
看到了没? 测试的时候 即便胡乱的输入URL(胡乱输入URL的路径 那肯定别的location都匹配不上了),最终都会匹配到 咱们的第一项默认location
因为 location / 指的是,只要你路径中以 / 开头了 不管你后面输入啥 都能匹配到(路径无论如何 都会以 / 起头)。于是返回201 (201是怎么自己设置的 为了观察)
好的 接下来 咱们看第一项和第二项和第三项在一起的情况
```plain
location / {
return 201;
}
location = /nginx {
return 202;
}
location /nginx {
return 203;
}
```
第二项这个 location = /nginx , 这里需要重点给大家强调一下了
单独一个 = 号, 代表的是 最高优先级+精准匹配 !!
什么意思呢? 首先最高优先级 意思是说,只要用户输入的URL 能匹配上这一条,那么其他的location一律全忽略掉(就算其他的也可以匹配上 都不管了) 只调用这里的
精准匹配 是什么意思呢? 就是 一点都不能差!
也就是说,= 后面写的内容 和 用户输入的路径 必须完全完全一模一样 ,稍有变化 则匹配不上了
接下来 咱们做实验吧
我们按照如下 这样输入URL, 返回202
\[root@server01 ~\]# curl -I [http://dami01.example.com/nginx](http://dami01.example.com/nginx)
HTTP/1.1 202 Accepted
Server: nginx/1.14.1
Date: Sat, 05 Jan 2019 05:06:13 GMT
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
返回202 说明被下面的location 匹配上了,而且是精准匹配 ,因为 用户输入的 /nginx 和 location = 后面的 /nginx 一模一样 ,多一个别的字符都没有
```plain
location = /nginx {
return 202;
}
```
一旦被 这个location的精确匹配抓到了,那么后面的 返回203的location 即便匹配上了 也没用了
接下来 咱们再换一种 输入方式
\[root@server01 ~\]# curl -I [http://dami01.example.com/nginx/](http://dami01.example.com/nginx/)
HTTP/1.1 203
Server: nginx/1.14.1
Date: Sat, 05 Jan 2019 05:09:32 GMT
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
\[root@server01 ~\]#
\[root@server01 ~\]# curl -I [http://dami01.example.com/nginxsdfasdf](http://dami01.example.com/nginxsdfasdf)
HTTP/1.1 203
Server: nginx/1.14.1
Date: Sat, 05 Jan 2019 05:09:39 GMT
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
注意 这次输入的URL有变化了, 第一个是 /nginx/ 第二个是 /nginxsdasdf
首先 ,这两次的输入 返回203 是被如下第三项location抓到了
```plain
location /nginx {
return 203;
}
```
为什么第二项location(返回202的) 抓不到了呢?
因为 这两次 咱们的输入 是 /nginx/ 和 /nginxsdasdf , 而 location = /nginx 可是眼睛里不揉沙子哦 必须输入的一模一样 多一个少一个字符都不行
而第三项 location /nginx 这个就相对宽松太多了, 它的意思是 只要用户输入的路径以 /nginx起始就行,后面的部分 随便输入什么 都无所谓 都可以匹配上
所以说 /nginx/ 和 /nginxsdasdf 这样的输入,都是满足了 前面以"/nginx"开头,后面再随意的输入
好的 接下来 咱们看 第四项和第五项规则
```plain
location ~ /Nginx/$ {
return 204;
}
location ~* /nginx/$ {
return 205;
```
先做一个简单的说明吧
location ~ 代表的是 区分大小写的正则表达式
location ~\* 代表的是 不区分大小写的正则表达式
204这一项代表的是,用户输入的nginx 必须开头字母大写,而且最后还必须是 '/'结尾 才能匹配上
($意思是 以某个字符结尾, /$ :以'/' 结尾)
而205这一项 代表的是 不区分大小写,但也得以 / 结尾
接下来咱们做实验吧
\[root@server01 ~\]# curl -I [http://dami01.example.com/Nginx/](http://dami01.example.com/Nginx/)
HTTP/1.1 204 No Content
Server: nginx/1.14.1
Date: Sat, 05 Jan 2019 05:27:07 GMT
Connection: keep-alive
\[root@server01 ~\]# curl -I [http://dami01.example.com/NgiNx/](http://dami01.example.com/NgiNx/)
HTTP/1.1 205
Server: nginx/1.14.1
Date: Sat, 05 Jan 2019 05:27:18 GMT
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
\[root@server01 ~\]# curl -I [http://dami01.example.com/ngiNx/](http://dami01.example.com/ngiNx/)
HTTP/1.1 205
Server: nginx/1.14.1
Date: Sat, 05 Jan 2019 05:27:25 GMT
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
第一次的测试,因为咱们完全符合了 204的规则,开头N大写,并且以/结尾 ,所以被204 location匹配抓到
后面的两次测试,由于我们没有遵守大小写的规定,但是依然是 nginx这个词,并且以/结尾,所以205location匹配到
再加入一项
```plain
location ~ \.jpg$ {
return 207;
}
```
这种代表的是 以.jpg结尾 我们当前的配置是
```plain
location / {
return 201;
}
location = /nginx {
return 202;
}
location /nginx {
return 203;
}
location ~ /Nginx/$ {
return 204;
}
location ~* /nginx/ {
return 205;
}
location ~ \.jpg$ {
return 206;
}
```
接下来 咱们来做实验吧
\[root@server01 ~\]# curl -I [http://dami01.example.com/1.jpg](http://dami01.example.com/1.jpg)
HTTP/1.1 206 Partial Content
Server: nginx/1.14.1
Date: Sat, 05 Jan 2019 05:40:43 GMT
Content-Type: image/jpeg
Content-Length: 0
Connection: keep-alive
\[root@server01 ~\]# curl -I [http://dami01.example.com/abc.jpg](http://dami01.example.com/abc.jpg)
HTTP/1.1 206 Partial Content
Server: nginx/1.14.1
Date: Sat, 05 Jan 2019 05:40:45 GMT
Content-Type: image/jpeg
Content-Length: 0
Connection: keep-alive
\[root@server01 ~\]#
\[root@server01 ~\]# curl -I [http://dami01.example.com/nginx/1.jpg](http://dami01.example.com/nginx/1.jpg)
HTTP/1.1 205
Server: nginx/1.14.1
Date: Sat, 05 Jan 2019 05:40:51 GMT
Content-Type: image/jpeg
Content-Length: 0
Connection: keep-alive
从上面 咱们可以看出,不管路径怎么随便输入,只要咱们的URL中 出现了以 '.jpg'结尾,就可以被206匹配到
但是注意最后一次的 当咱们的URL是 /nginx/1.jgp的时候, 返回的是205了
这就说明 ~\* /nginx/ 被匹配上了 并且 优先级 还比 ~ .jpg 高 ,所以 206被忽略了
大家私下里 就可以使用这种 location + return的方法 快速的自己测试 各种location匹配的方法 以及优先级
除此之外, location{} 括号之中的内容 ,可以单独定义 root , 也可以单独定义 proxy\_pass
比如,被某一个location匹配上后,就跳到某一个页面去,或者被反向代理到另一个位置去 等等
更多的内容 我们在下篇中 再继续 ^\_^
@@ -0,0 +1,287 @@
## 8.认识Nginx中的location和正则表达式,一起啃这块硬骨头(下篇)
\[location 匹配的优先级排序 和官方实例练习\]
上篇中 我们接触了 location的URL匹配实例 并有了一定的了解
(需要跟大家补充说明的一点是:在刚开始学习location的时候,尽量先使用return的方法 确认有没有正确匹配
而不要上来就把location和root放在一起使用练习,因为这两项有特殊的交互关系,咱们在这一篇中 会讲解)
location的匹配 符号主要使用的是如下这几种种
> 1, = 精确精准匹配
> 2, ~ 正则表达式匹配 区分大小写
> 3, ~\* 正则表达式匹配 不区分大小写
> 4, ^~ URL左半部分匹配 不区分大小写 (这个在上篇没有出现 )
> 5, 无匹配符号 (例如 location /nginx 这样)
那么接下来 咱们来看一下优先级的排序吧
正常情况下 优先级由高到低的顺序是这样排列的
1 > 4 > 2|3 > 5
精确匹配永远是最高优先级 这个没疑问
接下来的第二高优先级是 ~^ (一会我们用官方实例来解释 就很清楚了)
第三优先级 是 ~\* 或 ~ ,这两种只是区分大小写的区别 排在同一个优先级
最后的优先级是 无符号
接下来呢,咱们通过nginx官网提供的一段 实例来进行一个综合的解释吧,大家也请自己多多跟着练习
```coffeescript
location = / {
[ configuration A ]
}
location / {
[ configuration B ]
}
location /documents/ {
[ configuration C ]
}
location ^~ /images/ {
[ configuration D ]
}
location ~* \.(gif|jpg|jpeg)$ {
[ configuration E ]
}
```
请看上面这一段 location的配置
官方给出了如下这么一段使用案例
The “/” request will match configuration A, the “/index.html” request will match configuration B, the “/documents/document.html” request will match configuration C, the “/images/1.gif” request will match configuration D, and the “/documents/1.jpg” request will match configuration E.
我们来翻译并解释一下
如果用户输入的是 / 那么A配置生效 因为A是精确=匹配,最高优先级哦
如果用户输入的是 /index.html , B配置生效,因为A肯定不行了 因为只能是 '/' , 而后面三项也都匹配不上
如果用户输入是 /documents/document.html 呢? 配置C生效,因为当前只有一个/documents/ (请大家自己试试 如果同时再存在一个 localtion ~ /documents/的话 哪个生效吧 ^\_^)
如果用户输入 /images/1.gif 哪个生效呢? 回答是D 而不是E, 为什么呢? 咱们之前刚刚说过了, ^~这个匹配符号 是第二高优先级的,并且是URL左半部分匹配,意思就是说 URL 从/开始,有了images/到这里截止,后面的 就不再看了 直接生效 所以 E被直接忽略了
最后 /documents/1.jpg 呢 ? 匹配的是E ,而不是C 为啥? 因为C是无符号 排在最末尾的优先级哦
通过官方给的这样一个例子 相信大家对location匹配 更加清楚了一些
\[location和 root/alias的关系\]
这里是一个小难点了,我们一起来看一下吧
alias我们一会再说,先看root这里
root表示文件资源路径的指定,这个我们已经知道了
接下来就是 root和location 这一对儿存在什么样的关系了
我们先来看如下这一段的配置吧
```plain
location ~ /test/ {
root /www/root/html/;
}
```
如上这样配置的话
如果一个请求的URI是/test/a.html时,web服务器将会返回服务器上的/www/root/html/test/a.html的文件。
其实这个例子就足够说明问题了
root和location 其实是一种继承的关系, 也就是说, 你location后面写的路径 会被追加到root的路径的后面
也就是把 /test/ 放到 /www/root/html/test/ <- 这里来, 文件的寻找 会到这里去找
输入完整URL路径 /test/a.html 这个很好理解吧
不过 如果是走缺省的index主页呢?
例如 我现在输入的是 /test/ ,然后 你只要保证 在 /www/root/html/test/ 路径下 存在着 index主页即可 (比如 index.html)
之后自动就会补全访问到首页了
但是这里有一个 特例 请大家往下看这个例子
这个特例 就是精准匹配模式下的 location和root
```coffeescript
location = /login/ {
root /var/www/html/vhost/dami01.example.com/;
}
```
如果配置写成这个样子 就会造成一定的困扰了
为什么呢? 就是因为这个 = 精确匹配的问题
精确匹配= 我们知道 这是跟用户输入的URL必须一模一样 完全一致的
而大多数情况下 我们在访问各个网站的时候,一般并不会在URL中 输入到具体的xxx.html xxx.php 之类的 ,而是到/path/这样的一个路径就截止了
接下来 是默认主页地址的跳转
但是 如上这样的精确匹配的情况下, 就算 我们在 /var/www/html/vhost/dami01.example.com/login/ 下面 准备了好了 各种 a.html , index.html 等等
我们也访问不到
比如 如下的情况
![](assets/1593412375-d84d6c02990b6deff53e40f747dcf18a.png)
为什么会出现这种情况呢?
答案就在精确匹配的情况下, 必须精确到 URL后面 所有的部分才可以
比如输入/login/的情况下,感觉好像是跟location = /login/ 匹配上了,但事实上,后面被省略掉的主页地址 是会被自动填充上去 也计算在内
而如果我们输入 /login/a.html 或者 /login/index.html 这样的话,就又跟 location = /login/ 不匹配了
所以 在使用精确匹配的时候 一定要注意这个问题
如果 你location一定要使用精确匹配,那么就要按照如下这个样子 设定完整才可以访问到
```javascript
location = /login/b.php {
root /var/www/html/vhost/dami01.example.com/;
}
```
![](assets/1593412375-ca76a292a6e9c5cd91beb32accc1feff.png)
所以,大多数情况下 我们还是更提倡使用 其他的匹配符号 更为稳妥一些
```plain
location ~ /login/ {
root /var/www/html/vhost/dami01.example.com/;
}
```
![](assets/1593412375-26896f91ba3f9c50ddf6eedc5badcc9c.png)
看到了吗 ? 改成 ~ 匹配, 或者无符号匹配之后, 我们无论怎么输入路径 都可以轻松正确的访问到资源 ^\_^
明白了root & location的关系后 ,咱们再来看一下 alias这个东东怎么使用
如下的这一段配置
其实alias跟root 唯一不同点就是, root继承location后面的path, 而alias则丢弃
下面这样定以后,其实资源就在/var/www/html/vhost/dami01.example.com/ 下寻找,而不是 /var/www/html/vhost/dami01.example.com/aa/ 下寻找
这样清楚了吧? ^\_^
```plain
location ^~ /aa/ {
alias /var/www/html/vhost/dami01.example.com/;
}
```
![](assets/1593412375-de43dd98f18c52bb997acdef88018fa0.png)
```javascript
[location 和 proxy_path的联用]
location ^~ /server02/ {
proxy_pass http://server02:8080/server02.html;
}
location /mynginx/ {
proxy_pass http://nginx.org/;
}
```
如上面这样两段的配置,要跟大家说明的是
proxy\_pass的调用 就是反向代理到某一个地址去 可以接upstream的地址 也可以接上某一个URL地址
而proxy\_pass 和 location一起使用的时候, location匹配 和 proxy\_pass跳转 并没有继承路径的关系
也就是说,location这一行只要匹配上了, 下面的proxy\_pass单独被执行 ,不会继承路径之类的 (这个跟root使用 有区别)
```xml
[root@server01 ~]# curl http://dami01.example.com/server02/
hello this is server02
[root@server01 ~]# curl http://dami01.example.com/mynginx/
beta version has been released with
<a href="http://mailman.nginx.org/pipermail/unit/2018-January/000029.html">
regression fixes</a>.
</p></td></tr>
</table>
</div></div></body></html>
```
最后 还要注意的一点是 , proxy\_pass和loaction在一起使用的时候, 不支持location调用正则匹配符号
比如下面这样的配置
```plain
location ~* /mynginx/ {
proxy_pass http://nginx.org/;
}
```
会报出这样的错误
\[root@server01 ~\]# /usr/local/nginx/sbin/nginx -s reload
nginx: \[emerg\] "proxy\_pass" cannot have URI part in location given by regular expression, or inside named location, or inside "if" statement, or inside "limit\_except" block in /usr/local/nginx/conf/nginx.conf:65
所以,location这里 只能使用 ^~, = , 或者 无符号
(如果一定要使用正则表达式, 那么也有其他的方法可以实现 比如 nginx变量, 这个我们以后讲到了再提起吧)
\[正则表达式的练习\]
首先要说明的是 , 正则表达式 是一个独立的知识体系,并不局限在nginx 或者 shell 或者 其他任何技术中
正则表达式 需要长期的单独学习 才可以熟练掌握
我们在这里 简单介绍一下 学习和练习的方法
学习正则 肯定少不了 先背诵一些通用的 正则符号 都代表什么意思
> 1、^:匹配字符串的开始位置;
> 2、._: .匹配任意字符,_匹配数量0到正无穷;
> 3、. 斜杠用来转义,.匹配.;
> 4、(jpg|gif|png|bmp)匹配jpg或gif或png或bmp
> 5、$匹配字符串的结束位置;
> 6、i不区分大小写
> 7 \\d:数字
> 8 +:出现至少1次
> 9 .:任意字符,除换行和回车之外
> 10 ?:出现0或1次
> (.\\d+)?:括号里内出现0或1次
然后接下来 拿出几个例子 自己尝试看懂是啥意思
比如 ^\\d+(.\\d+) 是什么意思呢?
行开头、至少出现一次数字、(任意字符和至少出现一次数字)
另外, 正则表达式,除了在location中调用外, 还可以在 server\_name中调用的哦
比如下面的例子
什么意思呢?
server\_name 第一个,除了输入 标准的 www.example.com以外,还可以支持 类似输入
www72.example.com 这样的输入方式, \\d+ 意思是啥? 数字出现多次 ^\_^
```plain
server {
server_name www.example.com ~^www\d+\.example\.com$;
}
server {
server_name ~^(www\.)?(.+)$;
}
```
就按照这样的思路 来慢慢推荐学习 即可
@@ -0,0 +1,205 @@
## 9.HTTPS是大势所趋,必须学会如何配置
# \[什么是HTTPS\]
HTTP咱们已经熟悉了, 一个网址例如这样子 [http://abc.com/](http://abc.com/)
传统的HTTP站点有一个很大安全隐患,就是所有和用户传输的信息 都是明文 未经过加密 这样子很容易就被截取到重要的信息
所以 HTTPS应运而生
#那么什么又是HTTPS呢?
简单的说 HTTPS就是安全版的HTTP啦, HTTPS是由 HTTP + SSL , 也就是在HTTP上又加了一层处理加密信息的模块 ,所以传输的数据都是加密后的数据
https协议原理又是怎么回事呢?
首先,客户端与服务器建立连接,各自生成不同的私钥和公钥。
服务器返给客户端一个公钥,然后客户端拿着这个公钥把要搜索的东西加密,称之为密文,并连并自己的公钥一起返回给服务器,服务器拿着自己的私钥解密密文,然后把响应到的数据用客户端的公钥加密,返回给客户端,客户端拿着自己的私钥解密密文,把数据呈现出来
当然了, 具体的加密算法比较复杂 除非是专门搞安全加密的 不然 不需要太细致去了解加密的具体流程
我们只要知道,HTTPS为了让传送的数据加密,就必须依赖一种可以把数据加密解密 并且还可以保证 不同的用户使用不同的计算方法 ,保证每个用户的安全性 即可
但是这里有一个问题了
就算一个网站保证了 跟你之间的传输可以走 加密的模式, 但是 如果这个网站自身就是个钓鱼网站呢? 提供了加密解密又有什么意义,你的个人信息不还是一样 泄露给它们了?
所以 这就有了一个信任关系的问题了,网站可以提供加密解密,并且还得是一个值得信任的网站 才可以哦
这就有了另外的一个概念 叫做证书
证书用于验证一个网站的合法性,通常只能由第三方机构来颁发
可是,这个“第三方机构”到底是在哪呢?是一个远端服务?不可能吧?如果是个远端服务,整个交互都会慢了。所以,这个第三方机构的验证功能只能放在客户端的本地了。
当然了,我们在学习的过程中 ,没必要真的去购买一个证书,证书也是可以自签的哦
# \[重新编译我们的Nginx ,让它支持SSL\]
咱们之前 一直没讲的nginx自定义安装参数,都是采用默认的
到这里 我们就得返回头看一下这里了
首先,我们接着之前课程的nginx配置文件,做出如下这一点改动
```plain
location /mynginx/ {
proxy_pass https://nginx.org/;
}
```
其实就是把 [http://nginx.org/](http://nginx.org/) 改成 [https://nginx.org/](https://nginx.org/)
然后尝试reload一下?
```plain
[root@server01 nginx-1.14.1]# /usr/local/nginx/sbin/nginx -s reload
nginx: [emerg] https protocol requires SSL support in /usr/local/nginx/conf/nginx.conf:65
```
看到没有?
咱们都还没开始自己搭建https, 只不过是反向代理 指向到一个 https的URL, nginx就直接报错了
为啥呢?
因为咱们的nginx在安装的时候,就没有调用支持SSL的模块哦
接下来 ,我们得重新编译nginx了, 让它支持SSL才可以
先把 nginx.conf备份一下,然后跟着下面的步骤走
查看一下 当前的nginx有没有调用什么编译参数, 如果有的话 就一定都要带上 不然会出问题
(这个在工作中非常重要, 千万不要狗熊掰棒子,加入新参数,忘了原有的旧参数)
```coffeescript
[root@server01 nginx-1.14.1]# /usr/local/nginx/sbin/nginx -V
nginx version: nginx/1.14.1
built by gcc 4.4.7 20120313 (Red Hat 4.4.7-23) (GCC)
configure arguments:
```
好在这里咱们之前 没有加任何编译参数, 所以 我们只需要加入当前需要的就可以了
```javascript
./configure --prefix=/usr/local/nginx --with-http_ssl_module
```
指定安装路径 , 指定启用SSL模块
然后,make ; make install 即可
(请再三确认 pcre & pcre-devel 已经安装在你的linux了哦, 不然报错)
接下来 咱们试试reload
```coffeescript
[root@server01 nginx]# /usr/local/nginx/sbin/nginx -s reload
[root@server01 nginx]#
```
访问也没问题了
```makefile
[root@server01 nginx]# curl -I http://dami01.example.com/mynginx/
HTTP/1.1 200 OK
Server: nginx/1.14.1
Date: Thu, 31 Jan 2019 15:28:03 GMT
```
# \[接下来的任务 就是咱们自己创建HTTPS站点了哦\]
按照如下的步骤, 先生成密钥,再生成CSR, 最终生成证书 即可
注意 证书生成的位置,建议放在 /usr/local/nginx/conf/ssl 下面哦
```perl
[root@server01 nginx]# pwd
/usr/local/nginx
[root@server01 nginx]# mkdir conf/ssl ; cd conf/ssl
```
生成CSR文件
`openssl req -new -key server.key -out server.csr`
创建服务器证书的申请文件 server.csr
```php
[root@server01 ssl]# openssl req -new -key server.key -out server.csr
Enter pass phrase for server.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:BJ
Locality Name (eg, city) [Default City]:BJ
Organization Name (eg, company) [Default Company Ltd]:COMPANY
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:dami01.example.com
Email Address []:admin@admin.com
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
cp server.key server.key.org
```
生成证书文件server.crt
```css
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
```
到此为止, 我们的证书生成完毕
接下来,我们还需要改动nginx的配置文件 让我们的HTTPS可以正常运行起来
按照如下这样 , 我们新起一个server, 让它工作在默认的443端口(HTTPS默认端口)
之后,再指定证书和密钥的位置,指定我们在生成证书中的hostname(server\_name)
```plain
server {
listen 443;
ssl on;
ssl_certificate ssl/server.crt;
ssl_certificate_key ssl/server.key;
server_name dami01.example.com;
location / {
return 209;
}
}
```
重启nginx后 我们来测试一下
```makefile
[root@server01 nginx]# netstat -tnlp | grep 443
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 40603/nginx
[root@server01 nginx]# curl -k -I https://dami01.example.com/
HTTP/1.1 209
Server: nginx/1.14.1
Date: Thu, 31 Jan 2019 16:13:21 GMT
Content-Type: application/octet-stream
Content-Length: 0
Connection: keep-alive
```
这样, 我们的HTTPS站点 就建立成功啦 ^\_^
Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 857 KiB

Some files were not shown because too many files have changed in this diff Show More