感悟
在以前的运维过程中,大量环境都使用到了nginx,不管是与keepalived+haproxy做高可用,或者做缓存,或者做web应用服务器用来部署各种各样的环境,都使用到了它。但是真的了解它吗?并不,很多时候都是看一些博客里面的配置文件,或者在以前的配置文件上改改即用。所以对nginx的很多特性并不是很了解的。而作为一个运维,不仅要改善应用的稳定性,提升交付实施的效率,另外一方面就是提高资源的使用率节省成本。而nginx作为外端访问节点,这里面有很多文章可以做,所以有了重新学习nginx的欲望。
一、认识nginx
1.三个主要使用场景
静态资源
API服务
反向代理(缓存加速、负载均衡)
2.为什么会出现nginx?
访问量的增长
摩尔定律:性能提升
低效的apache
3.为什么要用nginx?
高并发、高性能
扩展性好,第三方模块多
BSD许可证
热部署、热升级
系统开销很小很小
4.nginx的组成
二进制文件
访问日志
错误日志
配置文件
5.版本发布情况以及主要特性和使用
官网连接:http://nginx.org/
主流版本:
官方版:社区nginx.org、商业版nginx.com
淘宝:Tengine
其它:社区OpenResty.org 商业OpenResty.com
编译安装nginx
————————————————————————————————
为什么选择编译而非apt-get或者yum?
因为直接下载的官方二进制文件不一定包含了我们想要的模块,我们如果需要定制化属于自己的模块,必须要重新编译后安装。基于此,我觉得不管是dockerfile或者本地部署nginx,都尽量使用编译安装。尤其是dockerfile,为符合docker极简思想,我们一定要编译最简单的nginx。为啥咧? 我经常看见很多老哥,经常说 为啥我的k8s服务器老是会磁盘报警?(你一个镜像1个G,每天发10个版本,你不报警谁报警?) 所以咧,如果用成本来考核一个运维,我觉得一个浪费资源的运维是不合格的,一定不是看过老马哥视频的。
————————————————————————————————
1.下载
http://nginx.org/download
2.编译
nginx-1.14.2
├── auto
├── CHANGES
├── CHANGES.ru
├── conf
├── configure
├── contrib
├── html
├── LICENSE
├── man
├── README
└── src
配置项的高亮:cp -r contrib/vim/* ~/.vim
复制此模块之后会显示出语法的高亮,便于配置nginx以及检查错误
configure的说明:
--help print this message
--prefix=PATH set installation prefix
--sbin-path=PATH set nginx binary pathname
--modules-path=PATH set modules path
--conf-path=PATH set nginx.conf pathname
--error-log-path=PATH set error log pathname
--pid-path=PATH set nginx.pid pathname
--lock-path=PATH set nginx.lock pathname
--user=USER set non-privileged user for
worker processes
--group=GROUP set non-privileged group for
worker processes
--build=NAME set build name
--builddir=DIR set build directory
--with 默认不会编译的模块
--without 默认编译的模块
中间模块 objs 主要的是modules 里面会看到里面的各种模块
最后使用make install 即可安装完成
配置nginx
1.重载
重载:nginx -s reload
热部署(nginx升级):
初始版本:
curl -I 127.0.0.1
HTTP/1.1 200 OK
Server: nginx/1.14.2
2.热部署
原理:新版本的nginx编译好后 只需要更换nginx的二进制文件即可
即下载新的安装包,按照上述流程重新编译 编译完成之后拷贝二进制文件(原来的复制为old) 再向原来的nginx的主进程发USR2信令 这个时候就会新起nginx
master以及worker进程来承担请求
curl -I 127.0.0.1
HTTP/1.1 200 OK
Server: nginx/1.15.8
Date: Tue, 08 Jan 2019 07:42:17 GMT
3.日志切割
做日志切割一方面是为了分析日志,另一方面是为了收集日志,我们可以在日志命名的时候再加上一些host信息,这样以后同步到ELK里面我们就能更准确的找到日志了,或者我们直接写到nginx的log模块里面去。也是为了后期维护的方便
A.编写脚本
#!/bin/bash
year=`date +%Y`
month=`date +%m`
day=`date +%d`
logs_backup_path="/usr/local/nginx/logs_backup/$year$month" #日志存储路径
logs_path="/usr/local/nginx/logs/" #要切割的日志路径
logs_access="access" #要切割的日志
logs_error="error"
pid_path="/usr/local/nginx/logs/nginx.pid" #nginx的pid
[ -d $logs_backup_path ]||mkdir -p $logs_backup_path
rq=`date +%Y%m%d`
#mv ${logs_path}${logs_access}.log ${logs_backup_path}/${logs_access}_${rq}.log
mv ${logs_path}${logs_error}.log ${logs_backup_path}/${logs_error}_${rq}.log
kill -USR1 $(cat /usr/local/nginx/logs/nginx.pid)
3.做定时任务
crontab –e
59 23 * * * bash /usr/local/nginx/shell/cut_ngnix_log.sh #每天23:59分开始执行;
二、nginx的简单使用
1.搭建静态资源服务器
以dlib库的帮助文档为例 我们来展示一下如何使用nginx做静态资源服务器
首先提供一下dlib库的链接:
地址:http://dlib.net/files/dlib-19.16.tar.bz2
解压之后的文件结构:
dlib-19.16/
├── CMakeLists.txt
├── dlib
├── docs
├── documentation.html
├── examples
├── ISSUE_TEMPLATE.md
├── MANIFEST.in
├── python_examples
├── README.md
├── setup.py
└── tools
这里啰嗦几句,在nginx里面 最大的是http,接下来在里面可以配置server,location,upstream等模块。
由于一个nginx可能要被多个服务所使用,为了便于管理,那么我们一般要拆分不同服务的配置文件,也就是说一个http模块下面要写多个server,在不同的server下写不同的location来实现模块的管理。
基于此,创建了vhost目录来用作配置文件管理:
include ../vhost/static.conf;
static.conf下面的内容:
server {
listen 8080;
server_name 47.244.20.89;
location / {
alias /www/docs/;
index index.html index.htm;
}
}
我们通常使用alias,alias和root的区别?
root会把url一些路径带到我们的文件目录里面来 从而导致一系列的404 所以通常用alias即可,这样就可以严格匹配我们的文件目录格式
设置好后访问即可:
发现什么问题没有?
文件是多大,那么我们的请求内容就是多大。
请求的资源大意味着什么?
在一个高并发的服务器上,每个请求都是会有内存的slab开销的,在使用公网服务器的时候是会有流量开销的。带宽资源的使用直接影响的就是用户的访问体感。nginx这个是后有个很好的参数 gzip on开启之后就能解决这个问题了。我们试试看:
gzip on; #开启开关
gzip_min_length 1; #小于1字节不再压缩 节省cpu
gzip_comp_level 2; #压缩级别是2
gzip_types tex/plain application/x-javascript text/css application/xml text/javascript applicat
ion/x-httpd-php image/jpeg image/gif image/png image/jpg;#执行压缩的文件类型
压缩后,我们的性能有了改观
header信息:
GET / HTTP/1.1
Host: 47.244.20.89:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
下面还有一点点要强调
alias 设置的目录是准确的,可以理解为linux的 ln命令创建软连接,location就是软连接的名字。如上面2.2例子访问 http://域名/down/vpser.txt 是直接访问的/home/wwwroot/lnmp/test/下面的vpser.txt文件。
root 设置的目录是根目录,locatoin里所指定名称的目录,必须在root设定下的目录有相同名字的目录。如果将上面2.2例子里的alias改成root 访问 http://域名/down/vpser.txt 是直接访问的的/home/wwwroot/lnmp/test/down/ 目录下的vpser.txt文件。
需要注意的是alias目录必须要以 / 结尾且alias只能在location中使用。
如果我们想要共享静态资源,可以使用文件浏览模式autoindex
如果想希望做出漂亮的目录列表,支持header,footer则可以安装三方插件:
http://wiki.nginx.org/NginxNgxFancyIndex
还有一种场景,nginx做为高并发web服务器,当有很多人在同时使用的时候,有的人在下毛片,有的人在登录等待后端服务响应,有的人正在打开主页,这些人都是在抢占资源的,我们如何保证打开一些必要的文件,比如首页渲染时要加载的css、js等能够优先加载出来呢?
可以考虑一波用set命令配合一些内置变量来实现功能。
比如 set $limit_rate 限速
location /autoindex {
alias /www/static/dlib/ ;
autoindex on; #做文件浏览
set $limit_rate 1k; #限速1k
可以明显看到加载了4.5k资源 用了5s 中间那点误差可能是跟服务器建连还有网络传输产生的 不用太纠结。
最后一点 logformat变量来记录日志
可以记录日志的参数列表:http://nginx.org/en/docs/http/ngx_http_core_module.html#http
Embedded Variables
格式定义:
log_format 名称 变量
引用:
access_log 名称
例如:
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
location /autoindex {
alias /www/static/dlib/ ;
autoindex on;
set $limit_rate 1k;
access_log /www/logs/access.log main;
}
验证一下生成的日志:
182.149.165.30 - - [23/Jan/2019:01:20:40 +0800] "GET /autoindex/ HTTP/1.1" 200 4048 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36" "-"
2.具备缓存的静态资源反向代理服务器
前置:1.5x 版本的nginx以及把请求代理到了后端的dlib服务上
后置:用另外一个1.4X nginx把请求代理到1.5X上面,并且在1.4X的nginx上建立缓存
nginx1.4---nginx1.5--alias
算法介绍:
一、分配方式
Nginx的upstream支持5种分配方式,下面将会详细介绍,其中,前三种为Nginx原生支持的分配方式,后两种为第三方支持的分配方式:
1、轮询
轮询是upstream的默认分配方式,即每个请求按照时间顺序轮流分配到不同的后端服务器,如果某个后端服务器down掉后,能自动剔除。
upstream backend {
server 192.168.1.101:8888;
server 192.168.1.102:8888;
server 192.168.1.103:8888;
}
2、weight
轮询的加强版,即可以指定轮询比率,weight和访问几率成正比,主要应用于后端服务器异质的场景下。
upstream backend {
server 192.168.1.101 weight=1;
server 192.168.1.102 weight=2;
server 192.168.1.103 weight=3;
}
3、ip_hash
每个请求按照访问ip(即Nginx的前置服务器或者客户端IP)的hash结果分配,这样每个访客会固定访问一个后端服务器,可以解决session一致问题。
upstream backend {
ip_hash;
server 192.168.1.101:7777;
server 192.168.1.102:8888;
server 192.168.1.103:9999;
}
4、fair
fair顾名思义,公平地按照后端服务器的响应时间(rt)来分配请求,响应时间短即rt小的后端服务器优先分配请求。
upstream backend {
server 192.168.1.101;
server 192.168.1.102;
server 192.168.1.103;
fair;
}
5、url_hash
与ip_hash类似,但是按照访问url的hash结果来分配请求,使得每个url定向到同一个后端服务器,主要应用于后端服务器为缓存时的场景下。
upstream backend {
server 192.168.1.101;
server 192.168.1.102;
server 192.168.1.103;
hash $request_uri;
hash_method crc32;
}
其中,hash_method为使用的hash算法,需要注意的是:此时,server语句中不能加weight等参数。
再回到我们的实验 这里不去深刨这些算法的实现以及思路了,不然得搞死去 知道这几种常用的算法的一般原理以及应用场景就OK
upstream test {
server 127.0.0.1:8080;
}
server {
listen 8888;
server_name localhost;
location / {
#proxy_set_header Host $host;
#proxy_set_header X-Real-IP $remote_addr;
#proxy_set_header X-forward-For $proxy_add_xforwarded_for;
proxy_pass http://test;
}
至此,反向代理完成
值得注意的是,做了反向代理,无论是waf,7层slb或cdn等,经过反向代理之后请求变成了 客户端--反向代理 反向代理--后端
那么后端拿到的就是反向代理的IP了,那么我现在就要通过重新设置
header变量的方式来解决这个问题了:
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-forward-For $proxy_add_xforwarded_for;
所有值得参考的代理参数:
http://nginx.org/en/docs/http/ngx_http_proxy_module.html
因为nginx的特性远远领先于上游服务器,那么我们就可以用它来做静态资源的缓存服务器。
当然 缓存也是在http区块里面做定义 在location里面做引用
http的定义:
proxy_cache_path /tmp/nginxcache levels=1:2 keys_zone=my_cache:10m max_size=10g
inactive=60m use_temp_path=off;
locaiton的引用:
proxy_cache my_cache; #定义的zone的名称
proxy_cache_key $host$uri$is_args$args;
proxy_cache_valid 200 304 302 1d;
设置好后停止服务:
3.日志可视化
这个方案太多了,elk是比较常用的一种,当然,如果机器不多,完全没必要上那么重型的处理方案,有个比较好用的工具可以分析我们的网站的应用情况:
https://goaccess.io/
4.ssl(secure sockets layer)的认识
用SSL是防止在中间链路劫持请求,因为整个秘钥交换要经历一下几个过程:
如何保证在通信之间证书是有公信力的呢?这个就需要证书颁发机构做签发了,这就是为什么我们申请到证书之后要提交域名等信息。因为浏览器请求https的时候是要去证书颁发机构做验证的,如果验证不通过的话,就会拦截请求,当然可以忽略这个拦截继续访问。
那么我们看一波,是如何做校验的。
证书的分类:
1 域名验证 DV
只校验域名信息 只要域名OK 浏览器就给通过 会有一把小绿锁
2 组织验证 OV
除了一把小绿锁 还有一个感叹号 里面会显示具体信息
3.扩展验证 EV
这个是最严的 也是最安全的 一般金融机构会用
可以看一波:https://support.google.com/chrome/?p=ui_security_indicator
作为一名阿里云工单支持者,我看过太多太多证书的各种问题导致的不能访问、被拦截、不守信等问题了,大概分几类:
1.客户端的问题导致的无法访问:
比如java客户端、比如安卓端
2.证书和域名不匹配:
比如阿里云SLB扩展域名没加转发策略
比如拿一个不支持通配符的域名当通配域名用
比如拿一个不知道什么玩意儿的证书拿去部署ssl
3.证书链丢失
部署的时候没有补齐证书链,访问的时候被浏览器降级拦截
作为一个曾经的金融狗,我就拿人人贷的证书来说吧,我们要先搞清楚证书是什么鬼:
在网络层的验证:
好了,何老师的习惯是喜欢结合事实来说话
无论是从网络层,还是浏览器,我们都看到了完整的三层证书,而所有ssl站点的证书都是由这三层构成的:
1.根证书
Certificate: 308205cc308204b4a00302010202106d7d50b3cb5515f024... (id-at-commonName=*.union.360.cn,id-at-stateOrProvinceName=345214227344272254345270202,id-at-localityName=345214227344272254345270202,id-at-organizationName
2.二级证书
Certificate: 308204b53082039da003020102021100ef051a741a1d9409... (id-at-commonName=WoSign OV SSL CA,id-at-organizationName=WoSign CA Limited,id-at-countryName=CN)
3.主证书
Certificate: 308204b43082039ca003020102021100939285400165715f... (id-at-commonName=Certum Trusted Network CA,id-at-organizationalUnitName=Certum Certification Authority,id-at-organizationName=Unizeto Technologies S.A.,id-at-countryName=PL)
根证书,一般微软、安卓或者ios这些操作系统一年才会更新一次他们的根证书库,关于根证书,微软有这样的描述:
使用“证书路径”选项卡,可以查看从所选证书到颁发证书的证书颁发机构 (CA) 的路径。
在证书被信任之前,Windows 必须验证证书是否来自受信任的源。此验证过程称作“路径验证”。
路径验证涉及按层次结构方式处理公钥证书及其颁发者证书,直到证书路径终止于受信任的自签名证书为止。通常这是根 CA 证书。如果路径中的证书之一有问题,或者找不到证书,则认为证书路径是不受信任的证书路径。
典型的证书路径包括根证书和一个或多个中间证书。通过单击“查看证书”,还可以了解有关路径中每个 CA 的证书的详细信息。
下面我们准备来测试部署一个SSL站点:
1.准备域名以及添加解析
2.配置let's encrypet
https://letsencrypt.readthedocs.io/en/latest/index.html
3.部署ssl证书
拉取安装脚本
git clone https://github.com/letsencrypt/letsencrypt
执行安装
cd letsencrypt/
./letsencrypt-auto
1、 手动验证 按照提示在申请证书的服务器上使用一个指定的URL提供一个指定的文件内容来进行验证,进行手动验证的服务器IP地址会被 Let's Encrypt 服务端记录在案。
2、 自动验证 在 目标服务器 (指域名解析对应的IP地址的服务器,下同)上运行客户端,并启动一个 80 或 443 端口进行自动验证。包括独立模式和其他web sever验证模式,在 Plugins 中详细解释
./letsencrypt-auto certonly --manual -d 域名
使用不同的模式来获取证书是因为,在安装的时候 程序需要打开你的服务器的地址
由于我们的geek.darylhx.cn其实就是目标服务器,所以我们直接执行
./letsencrypt-auto certonly --webroot --webroot-path /home/nginx_new/html/ -d geek.darylhx.cn --agree-tos --email hxcloud@gmail.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator webroot, Installer None
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for geek.darylhx.cn
Using the webroot path /home/nginx_new/html for all unmatched domains.
Waiting for verification...
Cleaning up challenges
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/geek.darylhx.cn/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/geek.darylhx.cn/privkey.pem
Your cert will expire on 2019-04-24. To obtain a new or tweaked
version of this certificate in the future, simply run
letsencrypt-auto again. To non-interactively renew *all* of your
certificates, run "letsencrypt-auto renew"
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
OJ8K!
证书路径:
/etc/letsencrypt/live/geek.darylhx.cn/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/geek.darylhx.cn/privkey.pem
lrwxrwxrwx 1 root root 42 Jan 25 01:18 /etc/letsencrypt/live/geek.darylhx.cn/privkey.pem -> ../../archive/geek.darylhx.cn/privkey1.pem
[root@iZj6cc5knv5romns4b9v12Z letsencrypt]# ll /etc/letsencrypt/live/geek.darylhx.cn/fullchain.pem
lrwxrwxrwx 1 root root 44 Jan 25 01:18 /etc/letsencrypt/live/geek.darylhx.cn/fullchain.pem -> ../../archive/geek.darylhx.cn/fullchain1.pem
同时也告诉了我们自动更新证书的方法:
letsencrypt-auto renew
我们可以把这个加个crontab啊 因为证书90天到期,所以我们可以30天更新一次
最后一步:配置nginx
生成2048位 DH parameters:
$ sudo openssl dhparam -out /etc/ssl/certs/dhparams.pem 2048
时间有点长,这个时候我们可以把配置文件先搞好
但是由于我们之前没编译ssl,所以无法使用ssl,这里我们来解锁一下热编译
加入需要安装的模块,重新编译,如这里添加–add-module=/data/software/ngx_http_google_filter_module
#./configure --prefix=/usr/local/nginx --with-http_ssl_module --with-http_sub_module --with-http_gzip_static_module --with-http_stub_status_module --add-module=/data/software/ngx_http_substitutions_filter_module --add-module=/data/software/ngx_http_google_filter_module
# make //千万不要make install,不然就真的覆盖了
好了,直接启动nginx吧
443端口已经启动,浏览器校验一下:
可以看到 我们这个90天的证书
关于证书的更新:
是基于certbot-auto这个脚本自动更新的,所以我们只需要把这个脚本放到定时任务里面去就好啦。设定个日期,比如30天40天执行一次。由于它更新的是我们的密钥文件,所以不会对站点有任何影响。