<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; LB</title>
	<atom:link href="https://blog.gmem.cc/tag/lb/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Thu, 16 Apr 2026 07:10:45 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>IPVS和Keepalived</title>
		<link>https://blog.gmem.cc/ipvs-and-keepalived</link>
		<comments>https://blog.gmem.cc/ipvs-and-keepalived#comments</comments>
		<pubDate>Wed, 28 Sep 2016 02:23:06 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[IPVS]]></category>
		<category><![CDATA[LB]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=22825</guid>
		<description><![CDATA[<p>IPVS 关于IPVS，可以参考这个网站：http://www.austintek.com/LVS/LVS-HOWTO/ 关于IPVS在内核中的实现，可以参考：IPVS模式下ClusterIP泄露宿主机端口的问题 简介 IPVS在内核中实现了传输层负载均衡，是一个L4的交换机。IPVS在一群真实服务器的前面，运行一个LB角色的主机，该主机面向客户端，提供了单一IP地址的虚拟服务。 和netfilter的交互 在Director上，LVS钩子在netfilter框架中的位置，不管是来程、回程报文，均从左侧进入、右侧出去。对于DR/TUN模式，回程报文不经过Director：  &#160; 当Director接收到封包时，如果它的目的地址是VIP，则PREROUTING后路由决策，会导致封包被发送到LOCAL_IN。这是因为VIP是Local地址，ip_vs_in挂钩到LOCAL_IN，是VIP必须在Director上的原因，因为只有目标是本地地址，才能走到LOCAL_IN LVS在LOCAL_IN注册钩子ip_vs_in，需要注意的是，钩子具有优先级。最低优先级的钩子，最先看见封包。LVS的钩子的优先级，比iptables规则高，因此iptables规则首先应用到封包，然后是LVS 上一步中，如果ip_vs_in得到封包，它会根据负载均衡算法，选择一个RS，然后如变魔法一般，将封包直接瞬移到POSTROUTING链 LVS不会在FORWARD中寻找入站封包，仅当NAT模式下，收到来自RS的回程报文时，LVS才和FORWARD链相关（NAT模式下回程报文的目的IP是客户端IP地址，是进不了LOCAL_IN的），封包在此unNATed（源地址从RS改为Director）。同样需要注意，LVS在任何iptables规则之后看到封包 负载均衡模式 只有fullNAT或者NAT才支持端口映射，也就是说VIP和RIP使用不同的端口。 fullNAT 不是标准内核的一部分。 来程报文： ClientIP: ClientPort - VIP:VPORT ⇨ IPVS Director ⇨ <a class="read-more" href="https://blog.gmem.cc/ipvs-and-keepalived">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/ipvs-and-keepalived">IPVS和Keepalived</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">IPVS</span></div>
<p>关于IPVS，可以参考这个网站：<a href="http://www.austintek.com/LVS/LVS-HOWTO/">http://www.austintek.com/LVS/LVS-HOWTO/</a></p>
<p>关于IPVS在内核中的实现，可以参考：<a href="/nodeport-leak-under-ipvs-mode#ipvs">IPVS模式下ClusterIP泄露宿主机端口的问题</a></p>
<div class="blog_h2"><span class="graybg">简介</span></div>
<p>IPVS在内核中实现了传输层负载均衡，是一个L4的交换机。IPVS在一群真实服务器的前面，运行一个LB角色的主机，该主机面向客户端，提供了单一IP地址的虚拟服务。</p>
<div class="blog_h2"><span class="graybg">和netfilter的交互</span></div>
<p>在Director上，LVS钩子在netfilter框架中的位置，不管是来程、回程报文，均从左侧进入、右侧出去。对于DR/TUN模式，回程报文不经过Director： <a href="https://cdn.gmem.cc/wp-content/uploads/2020/07/ipvs_netfilter.jpg"><img class="wp-image-33885 aligncenter" src="https://cdn.gmem.cc/wp-content/uploads/2020/07/ipvs_netfilter.jpg" alt="ipvs_netfilter" width="872" height="421" /></a></p>
<p>&nbsp;</p>
<ol>
<li>当Director接收到封包时，如果它的目的地址是VIP，则PREROUTING后路由决策，会导致封包被发送到LOCAL_IN。这是因为VIP是Local地址，ip_vs_in挂钩到LOCAL_IN，是VIP必须在Director上的原因，因为只有目标是本地地址，才能走到LOCAL_IN</li>
<li>LVS在LOCAL_IN注册钩子ip_vs_in，需要注意的是，钩子具有优先级。<span style="background-color: #c0c0c0;">最低优先级的钩子，最先看见封包</span>。LVS的钩子的优先级，比iptables规则高，<span style="background-color: #c0c0c0;">因此iptables规则首先应用到封包，然后是LVS</span></li>
<li>上一步中，如果ip_vs_in得到封包，它会根据负载均衡算法，选择一个RS，然后如变魔法一般，<span style="background-color: #c0c0c0;">将封包直接瞬移</span>到POSTROUTING链</li>
<li>LVS不会在FORWARD中寻找入站封包，仅当NAT模式下，收到来自RS的回程报文时，LVS才和FORWARD链相关（NAT模式下回程报文的目的IP是客户端IP地址，是进不了LOCAL_IN的），封包在此unNATed（源地址从RS改为Director）。同样需要注意，LVS在任何iptables规则之后看到封包</li>
</ol>
<div class="blog_h2"><span class="graybg"><a id="ipvs-mode"></a>负载均衡模式</span></div>
<p>只有fullNAT或者NAT才支持端口映射，也就是说VIP和RIP使用不同的端口。</p>
<div class="blog_h3"><span class="graybg">fullNAT</span></div>
<p>不是标准内核的一部分。</p>
<p>来程报文： ClientIP: ClientPort - VIP:VPORT ⇨<em> IPVS Director</em> ⇨ <strong><span style="background-color: #339966;">DirectorIP</span></strong>: <strong><span style="background-color: #339966;">DirectorPort</span></strong> - <span style="background-color: #ff0000;"><strong>RIP</strong></span>：<span style="background-color: #ff0000;"><strong>RPORT</strong></span></p>
<p>回程报文：RIP: RPORT - DirectorIP: DirectorPort ⇨ IPVS Director ⇨ <span style="background-color: #ff0000;"><strong>VIP</strong></span>:<span style="background-color: #ff0000;"><strong>VPORT</strong></span> - <strong><span style="background-color: #339966;">ClientIP</span></strong>: <strong><span style="background-color: #339966;">ClientPort</span></strong></p>
<p>和NAT模式相比，IPVS Director在进行DNAT的同时，还进行SNAT。这样获得以下优势：</p>
<ol>
<li>客户端可以和Director、真实服务器在同一个局域网内</li>
</ol>
<p>缺点是：</p>
<ol>
<li>真实服务器<span style="background-color: #c0c0c0;">看不到客户端的真实IP</span>地址。只能由Director通过TCP Options携带真实IP，但是TCP Option只有40字节，可能客户端已经占用导致空间不足</li>
</ol>
<div class="blog_h3"><span class="graybg">NAT</span></div>
<p>来程报文： ClientIP: ClientPort - VIP:VPORT ⇨<em> IPVS Director</em> ⇨ ClientIP: ClientPort - <strong><span style="background-color: #ff0000;">RIP</span></strong>：<strong><span style="background-color: #ff0000;">RPORT</span></strong></p>
<p>回程报文：RIP: RPORT - ClientIP: ClientPort ⇨ IPVS Director ⇨ <strong><span style="background-color: #ff0000;">VIP</span></strong>:<strong><span style="background-color: #ff0000;">VPORT</span></strong> - ClientIP: ClientPort</p>
<p>基于NAT的虚拟服务器，当LB具有两个网络接口时：</p>
<ol>
<li>一个接口分配面向外部的IP地址</li>
<li>另一个接口分配私有的、面向内部的IP地址</li>
</ol>
<p>LB从外部接口接受流量，然后经过NAT，转发给内部网络中的真实服务器。</p>
<p>该模式的缺陷：</p>
<ol>
<li>LB是性能瓶颈，它需要转发两个方向的流量</li>
<li><span style="background-color: #c0c0c0;">RS上必须进行适当的路由</span>，确保针对ClientIP的路由转发给IPVS Director处理</li>
<li><span style="background-color: #c0c0c0;">客户端必须和RS不在同一网段</span>。这个限制可以通过配置解决：
<ol>
<li>在Director上关闭ICMP重定向：<br />
<pre class="crayon-plain-tag">echo 0 &gt; /proc/sys/net/ipv4/conf/all/send_redirects
echo 0 &gt; /proc/sys/net/ipv4/conf/default/send_redirects
echo 0 &gt; /proc/sys/net/ipv4/conf/eth0/send_redirects</pre>
</li>
<li>在RS上，把Director配置为出口包的唯一网关</li>
</ol>
</li>
</ol>
<div class="blog_h3"><span class="graybg">Tunneling</span></div>
<p>在此模式下，LB通过IP隧道将请求发送给真实服务器。</p>
<p>好处是可扩容，LB将请求转发给真实服务器后，<span style="background-color: #c0c0c0;">真实服务器直接将响应发送给客户端，不再需要经过LB</span>。由于大部分情况下都是请求包小、应答包大，这种模式下LB的性能压力较小。</p>
<p>该模式的缺陷：对网络结构依赖很大，<span style="background-color: #c0c0c0;">真实服务器必须支持IP隧道协议</span>。</p>
<div class="blog_h3"><span class="graybg">Direct Routing</span></div>
<p>用户请求LB上的VIP，LB将请求直接转发（通过修改L2封包的方式，L3保持不变）给真实服务器。</p>
<p>好处是可扩容，而且没有Tunneling模式的封包解包开销。</p>
<p>真实服务器要直接应答客户端，则应答包的源IP必须是VIP。这就导致LB和所有真实服务器都需要共享VIP+MAC的组合。不经过合理配置，则真实服务器可能直接接收到请求，绕过LB。</p>
<p>该模式的缺陷：</p>
<ol>
<li>RS和LVS的VIP，必须使用<span style="background-color: #c0c0c0;">相同端口</span></li>
<li><span style="background-color: #c0c0c0;">RS和LVS不能在同一机器上</span>，否则该RS访问VIP不经过LVS的负载均衡，而是直接访问自己</li>
<li>RS和LVS必须位于<span style="background-color: #c0c0c0;">同一VLAN</span>或局域网</li>
</ol>
<div class="blog_h2"><span class="graybg">负载均衡算法</span></div>
<div class="blog_h3"><span class="graybg">rr</span></div>
<p>简单的轮询算法，均等地对待每一台服务器,而不管服务器上实际的连接数和系统负载。</p>
<div class="blog_h3"><span class="graybg">wrr</span></div>
<p>带权重的轮询算法，可以保证处理能力强的服务器处理更多的访问流量?调度器可以自动问询真实服务器的负载情况,并动态地调整其权值。</p>
<div class="blog_h3"><span class="graybg">lc</span></div>
<p>IPVS表存储了所有活动的连接，基于此信息可以将请求发送给具有<span style="background-color: #c0c0c0;">最少已建立连接</span>的RS。如果集群系统的<span style="background-color: #c0c0c0;">真实服务器具有相近的系统性能，采用"最小连接"调度算法可以较好地均衡负载</span>。</p>
<div class="blog_h3"><span class="graybg">wlc</span></div>
<p>考虑权重的最少连接算法，具有较高权值的服务器将承受较大比例的活动连接负载。</p>
<div class="blog_h3"><span class="graybg">lblc</span></div>
<p>通常用于缓存集群，将来自同一个目的地址的请求分配给同一台RS，如果RS满载则将这个请求分配给连接数最小的RS。</p>
<div class="blog_h3"><span class="graybg">lblcr</span></div>
<p>根据请求的目标IP地址找出该目标IP地址对应的服务器组，按“最小连接”原则从该服务器组中选出一台服务器，若</p>
<ol>
<li>服务器没有<span style="background-color: #c0c0c0;">超载， 将请求发送到该服务器</span></li>
<li>若服务器超载；则按“最小连接”原则从整个集群中选出一台服务器，将该服务器加入到服务器组中，将请求发送到该服务器</li>
</ol>
<p>当该服务器组有一段时间没有增减成员，将<span style="background-color: #c0c0c0;">最忙的服务器从服务器组中删除</span></p>
<div class="blog_h3"><span class="graybg">sh</span></div>
<p>源地址哈希调度以源地址为关键字查找一个静态hash表来获得需要的RS</p>
<div class="blog_h3"><span class="graybg">dh</span></div>
<p>目的地址哈希调度以目的地址为关键字查找一个静态hash表来获得需要的RS </p>
<div class="blog_h3"><span class="graybg">nq</span></div>
<p>如果存在空闲服务器，则请求转发给它；否则，按seq算法</p>
<div class="blog_h3"><span class="graybg">seq</span></div>
<p>最短期望延迟（Shortest Expected Delay）。期望延迟公式： (Ci + 1) / Ui ，其中Ci为服务器i的当前连接数，Ui为服务器i的固定服务速率（权重）</p>
<div class="blog_h2"><span class="graybg">ipvsadm命令</span></div>
<p>管理Linux虚拟服务器的底层命令行工具，可以创建、修改、查看位于Linux内核中的虚拟服务器表</p>
<div class="blog_h3"><span class="graybg">格式</span></div>
<pre class="crayon-plain-tag"># 添加或修改虚拟服务
ipvsadm -A|E virtual-service [-s scheduler] [-p [timeout]] [-M netmask] [-b sched-flags]
# virtual-service可以是：
#  -t, --tcp-service service-address
#  -u, --udp-service service-address
# 示例
ipvsadm -A -t 10.0.10.100:80 -s rr -p 600

# 删除虚拟服务
ipvsadm -D virtual-service

# 清空虚拟服务表
ipvsadm -C

# 从标准输入还原虚拟服务表
ipvsadm -R

# 将虚拟服务表导出到标准输出
ipvsadm -S [-n]

# 为虚拟服务添加、编辑一个真实服务器
# 封包转发方式：
# -g --gatewaying 直接路由
# -i, --ipip  隧道（IPIP封装）
# -m, --masquerading NAT
ipvsadm -a|e virtual-service -r server-address [-g|i|m] [-w weight] [-x upper] [-y lower]
# 示例
ipvsadm -a -t 10.0.10.100:80 -r 10.1.0.10:80 -m

# 为虚拟服务删除一个真实服务器
ipvsadm -d virtual-service -r server-address
# 示例
ipvsadm -d -t 10.0.10.100:80 -r 10.1.0.10:80

# 列出虚拟服务
ipvsadm -L|l [virtual-service] [options]

# 清零包、字节、速率计数器
ipvsadm -Z [virtual-service]

# 设置IPVS连接的超时值，三个值，分别用于TCP会话、接收到FIN后的TCP会话、UDP包
ipvsadm --set tcp tcpfin udp

# 启动连接同步守护进程，state可以为master或backup。连接同步守护进程在内核中运行
# 主守护进程周期性的组播连接的变更，备份守护进程则接收组播消息并创建对应的连接。一旦主宕机，则从具有几乎全部的连接，不会出现已创建连接损坏的情况
ipvsadm --start-daemon state [--mcast-interface interface] [--syncid syncid]

# 停止连接同步守护进程
ipvsadm --stop-daemon state</pre>
<div class="blog_h3"><span class="graybg">选项</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 150px; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>-s alg</td>
<td>负载均衡算法，即分别TCP连接或UDP报到RS的方法</td>
</tr>
<tr>
<td>-p [timeout]</td>
<td>指示虚拟服务是否“持久的”。如果指定该选项，则同一客户端的后续请求，被转发给同一个RS</td>
</tr>
<tr>
<td>-M netmask</td>
<td>持久虚拟服务的客户端被分组的粒度，可以将某个网络的所有客户端都转发给同一RS </td>
</tr>
<tr>
<td>-r server-address</td>
<td>指定真实服务器的地址 </td>
</tr>
<tr>
<td>-g</td>
<td>使用DR模式 </td>
</tr>
<tr>
<td>-i</td>
<td>使用隧道模式，即ipip封装</td>
</tr>
<tr>
<td>-m</td>
<td>使用NAT模式</td>
</tr>
<tr>
<td>-w</td>
<td>指定服务器的相对权重，整数</td>
</tr>
<tr>
<td>-t service-address</td>
<td>使用TCP服务</td>
</tr>
<tr>
<td>-u service-address</td>
<td>使用UDP服务</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">示例</span></div>
<pre class="crayon-plain-tag"># 创建TCP虚拟服务207.175.44.110:80，使用轮询调度
ipvsadm -A -t 207.175.44.110:80 -s rr
# 添加两台RS
ipvsadm -a -t 207.175.44.110:80 -r 192.168.10.1:80 -m
ipvsadm -a -t 207.175.44.110:80 -r 192.168.10.2:80 -m


# 打印连接列表，可以看到IPVS建立的连接的状态
ipvsadm -lc

# IPVS connection entries
# pro expire state       source             virtual            destination
# TCP 01:19  FIN_WAIT    zircon:33568       k8s:6443           neon:6443
# TCP 119:57 ESTABLISHED zircon:33570       k8s:6443           xenon:6443
# TCP 01:19  FIN_WAIT    zircon:33569       k8s:6443           radon:6443</pre>
<div class="blog_h1"><span class="graybg">Keepalived</span></div>
<div class="blog_h2"><span class="graybg">简介</span></div>
<p>Keepalived提供负载均衡和高可用的框架。</p>
<p>负载均衡基于IP虚拟服务器（IPVS）内核模块，支持L4负载均衡。Keepalived提供了一组健康检查器，可以动态、自适应的管理上游服务器池。</p>
<p>高可用基于虚拟路由冗余协议（VRRP）。Keepalived实现了一系列钩子，和VRRP状态机进行底层/高层交互。</p>
<p>出于健壮性的考虑，Keepalived被拆分为三个进程：</p>
<ol>
<li>一个最小化的父进程，负责监控子进程</li>
<li>两个子进程，分别负责VRRP、上游服务健康检查</li>
</ol>
<div class="blog_h2"><span class="graybg">安装</span></div>
<p>通过包管理器安装：</p>
<pre class="crayon-plain-tag"># CentOS
yum install keepalived
# Ubuntu
apt install libipset-dev
apt install keepalived</pre>
<p>完成配置工作后，执行下面的命令启动：</p>
<pre class="crayon-plain-tag">systemctl restart keepalived.service</pre>
<div class="blog_h2"><span class="graybg">keepalived命令</span></div>
<div class="blog_h3"><span class="graybg">选项</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>f, –use-file=FILE</td>
<td>指定配置文件路径</td>
</tr>
<tr>
<td>-P, –vrrp</td>
<td>仅仅运行VRRP子系统，也就是仅仅提供HA</td>
</tr>
<tr>
<td>-C, –check</td>
<td>仅仅运行健康检查子系统，也就是仅仅提供LB</td>
</tr>
<tr>
<td>-l, –log-console</td>
<td>打印日志到控制台，默认打印到syslog</td>
</tr>
<tr>
<td>-D, –log-detail</td>
<td>记录详细日志</td>
</tr>
<tr>
<td>-S, –log-facility=[0-7]</td>
<td>日志级别</td>
</tr>
<tr>
<td>-V, –dont-release-vrrp</td>
<td>进程退出后，不移除VRRP虚IP和VROUTE</td>
</tr>
<tr>
<td>-I, –dont-release-ipvs</td>
<td>进程退出后，不移除IPVS拓扑 </td>
</tr>
<tr>
<td>-n, –dont-fork </td>
<td>前台运行</td>
</tr>
<tr>
<td>-d, –dump-conf</td>
<td>导出配置文件</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">配置语法2.0</span></div>
<p>在Ubuntu 16.04上，配置文件的位置为/etc/keepalived/keepalived.conf。</p>
<div class="blog_h3"><span class="graybg">全局定义</span></div>
<pre class="crayon-plain-tag">global_defs {
    # 通知邮件配置
    notification_email {
        email
        email
    }
    notification_email_from email
    smtp_server host
    smtp_connect_timeout num
    # LVS Director的名字
    lvs_id string
}</pre>
<div class="blog_h3"><span class="graybg">虚拟服务器定义</span></div>
<pre class="crayon-plain-tag"># 提示此虚拟服务器是一个FWMARK
virtual_server (@IP PORT)|(fwmark num) {
    # 健康检查周期，秒
    delay_loop num
    # 负载均衡算法（调度算法）
    lb_algo rr|wrr|lc|wlc|sh|dh|lblc
    # IPVS模式
    lb_kind NAT|DR|TUN
    (nat_mask @IP)
    # 持久化连接，即会话亲和，让一段时间内同一客户端的连接（短）都发送到同一个RS
    # 持久化连接的超时
    persistence_timeout num
    # 持久化连接的粒度掩码
    persistence_granularity @IP
    # 用于HTTP/SSL_GET的虚拟主机名
    virtualhost string
    # 协议类型
    protocol TCP|UDP
    # 如果所有RS宕机，将下面的服务器加入池中
    sorry_server @IP PORT
    # 定义一个真实服务器（RS）
    real_server @IP PORT {
        # 负载均衡权重
        weight num
        # 通过TCP进行健康检查
        TCP_CHECK {
            connect_port num
            connect_timeout num
        }
    }
    real_server @IP PORT {
        weight num
        # 通过脚本进行健康检查
        MISC_CHECK {
            misc_path /path_to_script/script.sh
            (or misc_path “ /path_to_script/script.sh &lt;arg_list&gt;”)
        }
    }
}
real_server @IP PORT {
    weight num
    # 通过HTTP/HTTPS进行健康检查
    HTTP_GET|SSL_GET {
        # 可以指定多个url块
        url {
            # URL路径
            path alphanum
            # 摘要信息
            digest alphanum
        }
        # 端口
        connect_port num
        # 超时
        connect_timeout num
        # 重试次数
        retry num
        # 重试延迟
        delay_before_retry num
    }
}</pre>
<div class="blog_h3"><span class="graybg">VRRP定义</span></div>
<pre class="crayon-plain-tag"># 组
vrrp_sync_group string {
    group {
        string
        string
    }
    # 可以为脚本传递参数，整体用引号包围
    notify_master /path_to_script/script_master.sh
    notify_backup /path_to_script/script_backup.sh
    notify_fault /path_to_script/script_fault.sh
}

# 默认情况下，Keepavelid只能对网络故障、Keepalived自身故障进行监控，并依此进行Master切换
# 使用vrrp_script则可以通过脚本进行自定义的（对本节点）健康检查

vrrp_script chk {
   script "/bin/bash -c 'curl -m1 -k -s https://127.0.0.1:6443/healthz -o/dev/null'"
   # 每两秒执行一次
   interval 2
   # 如果检测成功（脚本结果为0），且weight大于0，则当前实例的优先级升高
   # 如果检测失败（脚本结果为非0），且weight小于0，则当前实例的优先级降低
   weight -10
   # 连续3次为0才认为成功
   fall 3
   # 连续1次失败则认为失败
   rise 1
}
# 实例
vrrp_instance string {
    # 实例状态
    # 如果所有实例都配置为BACKUP，则初始时高优先级的成为第一个Master
    # 如果一个设置为MASTER其它设置为BACKUP，那么不设置nopreempt时每当Master恢复都会强占成为主
    state MASTER|BACKUP
    # 高优先级的实例恢复正常后，不会去强占，成为Master
    nopreempt
    # 使用的网络接口
    interface string
    # &lt;span style="color: #404040;" data-mce-style="color: #404040;"&gt;VRRP通知的IP头上的IP地址&lt;/span&gt;
    mcast_src_ip @IP
    # LVS sync_daemon在什么网络接口上运行
    lvs_sync_daemon_interface string
    # 此实例所属的虚拟路由器ID
    virtual_router_id num
    # 优先级，其运行时值可以随着vrrp_script的检测结果动态变化
    # 如果接收到Peer的vrrp广播包，发现自己的优先级最高，则自动切换为Master
    priority num
    # VRRP通知间隔
    advert_int num
    # 当MASTER状态变化时进行邮件通知
    smtp_alert
    # VRRP身份验证配置
    authentication {
        auth_type PASS|AH
        auth_pass string
    }
    # 定义虚IP
    virtual_ipaddress {
        @IP
        @IP
        @IP
    }
    # 排除虚IP
    virtual_ipaddress_excluded {
        @IP
        @IP
        @IP
    }
    # 进入MASTER状态后执行的脚本
    notify_master /path_to_script/script_master.sh
    # 进入BACKUP状态后执行的脚本
    notify_backup /path_to_script/script_backup.sh
    # 进入FAULT状态后执行的脚本
    notify_fault /path_to_script/script_fault.sh
}</pre>
<div class="blog_h2"><span class="graybg">配置语法1.2</span></div>
<div class="blog_h3"><span class="graybg">全局定义</span></div>
<pre class="crayon-plain-tag">global_defs {
    notification_email {
        admin@example1.com
    }
    smtp_server 127.0.0.1 [&lt;PORT&gt;]
    smtp_helo_name &lt;HOST_NAME&gt;
    smtp_connect_timeout 30
    router_id my_hostname    # 机器标识
    vrrp_mcast_group4 224.0.0.18  # VRRP组播地址
    vrrp_mcast_group6 ff02::12
    default_interface eth0   # 静态地址的默认网络接口
    # LVS连接同步守护进程的配置
    #               监听接口     对应VRRP实例      lvs syncd的ID  最大包长度       使用的UDP端口               组播地址
    lvs_sync_daemon &lt;INTERFACE&gt; &lt;VRRP_INSTANCE&gt; [id &lt;SYNC_ID&gt;] [maxlen &lt;LEN&gt;] [port &lt;PORT&gt;] [ttl &lt;TTL&gt;] [group &lt;IP ADDR&gt;]
    lvs_timeouts tcp 7200 tcpfin 120 udp 300  # LVS超时配置
    lvs_flush # 启动时清空所有LVS配置
    vrrp_garp_master_delay 5   # 转变为MASTER后多久发起ARP宣告
    vrrp_garp_master_repeat 5  # 发起ARP宣告的次数
    vrrp_garp_master_refresh 0 # ARP重新宣告的周期，默认0表示不重复宣告
    vrrp_garp_master_refresh_repeat 1 # 重新宣告时发送ARP的次数
    vrrp_version 2 # 使用的VRRP协议版本
}</pre>
<div class="blog_h1"><span class="graybg">DR模式示例</span></div>
<p>本示例的Keepalived版本为1.2.24。</p>
<div class="blog_h2"><span class="graybg">关于DR模式</span></div>
<p>使用DR模式时，必须保证：</p>
<ol>
<li>RS本机必须有网络接口，配置为VIP。DR模式下LB节点仅仅修改以太网帧的目标MAC地址，IP包不做任何修改，因此RS必须作为IP包的Destination</li>
<li>RS不去响应针对VIP的ARP请求</li>
<li>RS不去发送关于VIP的ARP通知</li>
</ol>
<p>要保证上面三点，需要调整RS内核的行为。具体实施方法有几种：</p>
<div class="blog_h3"><span class="graybg">添加arptables规则</span></div>
<pre class="crayon-plain-tag">arptables -A IN -d 10.0.10.1 -j DROP
arptables -A OUT -s 10.0.10.1 -j mangle --mangle-ip-s 10.0.10.1 </pre>
<div class="blog_h3"><span class="graybg">修改内核参数</span></div>
<p><span class="graybg">关于内核参数/proc/sys/net/ipv4/conf/*/arp_ignore：</span></p>
<p>DR模式下，每个真实服务器节点都要在环回网卡上绑定VIP。如果客户端对于VIP的ARP请求广播到了各个真实服务器节点，当：</p>
<ol>
<li>arp_ignore=0，则各个真实服务器节点都会响应该arp请求，此时客户端就无法正确获取LVS（LB）节点上正确的VIP所在网卡的MAC地址。客户端可能将以太网帧绕过LB直接发给RS</li>
<li>arp_ignore=1，只响应目的IP地址为接收网卡上的本地地址的arp请求</li>
</ol>
<p>关于内核参数/proc/sys/net/ipv4/conf/*/arp_announce：</p>
<p>每台服务器或者交换机中都有一张arp表，该表用于存储对端通信节点IP地址和MAC地址的对应关系。当</p>
<ol>
<li>收到一个未知IP地址的arp请求，就会在本机的arp表中新增对端的IP和MAC记录</li>
<li>当收到一个已知IP地址（arp表中已有记录的地址）的arp请求，则会根据arp请求中的源MAC刷新自己的arp表</li>
</ol>
<p>如果arp_announce参数配置为0，则网卡在发送arp请求时，可能选择的源IP地址并不是该网卡自身的IP地址，这时候收到该arp请求的其他节点或者交换机上的arp表中记录的该网卡IP和MAC的对应关系就不正确，可能会引发一些未知的网络问题，存在安全隐患。</p>
<p>DR模式下要求arp_ignore参数配置为1，arp_announce参数配置为2：</p>
<pre class="crayon-plain-tag"># 修改所有RS的内核参数
echo "1" &gt;/proc/sys/net/ipv4/conf/lo/arp_ignore
echo "2" &gt;/proc/sys/net/ipv4/conf/lo/arp_announce
echo "1" &gt;/proc/sys/net/ipv4/conf/all/arp_ignore
echo "2" &gt;/proc/sys/net/ipv4/conf/all/arp_announce

# 在环回网卡上绑定虚拟IP
ifconfig lo:k8s  10.0.10.1 netmask 255.255.255.255 broadcast 10.0.10.1
ifconfig lo:etcd 10.0.10.1 netmask 255.255.255.255 broadcast 10.0.10.1
ifconfig lo:ceph 10.0.10.1 netmask 255.255.255.255 broadcast 10.0.10.1</pre>
<div class="blog_h2"><span class="graybg">keepalived配置</span></div>
<pre class="crayon-plain-tag">global_defs {
    router_id oxygen

    # 增加LVS超时
    # 可以防止TCP连接静置一段时间后服务器返回RST
    # 可通过ipvsadm -l --timeout确保生效
    # 设置超时大于7200，因为TCP保活定时器默认2小时执行，保持行为一致
    lvs_timeouts tcp 7200 tcpfin 120 udp 300
    lvs_sync_daemon eth0 51
}

vrrp_instance apiserver {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass gmem
    }
    virtual_ipaddress {
        10.0.10.1/32 brd 10.0.10.1 dev eth0 label eth0:k8s
        10.0.10.2/32 brd 10.0.10.2 dev eth0 label eth0:etcd
        10.0.10.3/32 brd 10.0.10.3 dev eth0 label eth0:ceph
    }
}

virtual_server 10.0.10.1 6443 {
    delay_loop 5
    lb_algo wlc
    lb_kind DR
    protocol TCP
    real_server 10.0.5.1 6443 {
        weight 10
        SSL_GET {
            url {
                path /healthz
                status_code 200
            }
            connect_timeout 3
            nb_get_retry 3
            delay_before_retry 3
        }

    }
    real_server 10.0.2.1 6443 {
        weight 10
        SSL_GET {
            url {
                path /healthz
                status_code 200
            }
            connect_timeout 3
            nb_get_retry 3
            delay_before_retry 3
        }

    }
    real_server 10.0.3.1 6443 {
        weight 10
        SSL_GET {
            url {
                path /healthz
                status_code 200
            }
            connect_timeout 3
            nb_get_retry 3
            delay_before_retry 3
        }

    }

}

virtual_server 10.0.10.2 2379 {
    delay_loop 5
    lb_algo wlc
    lb_kind DR
    protocol TCP
    real_server 10.0.5.1 2379 {
        weight 10
        TCP_CHECK {
          connect_timeout 3
        }
    }
    real_server 10.0.2.1 2379 {
        weight 10
        TCP_CHECK {
          connect_timeout 3
        }
    }
    real_server 10.0.3.1 2379 {
        weight 10
        TCP_CHECK {
          connect_timeout 3
        }
    }

}

virtual_server 10.0.10.3 6789 {
    delay_loop 5
    lb_algo wlc
    lb_kind DR
    protocol TCP
    # 要启用持久化连接，必须配置
    persistence_timeout 300
    real_server 10.0.5.1 6789 {
        weight 10
        TCP_CHECK {
          connect_timeout 3
        }
    }
    real_server 10.0.2.1 6789 {
        weight 10
        TCP_CHECK {
          connect_timeout 3
        }
    }
    real_server 10.0.3.1 6789 {
        weight 10
        TCP_CHECK {
          connect_timeout 3
        }
    }

}</pre>
<p>在此配置下：</p>
<ol>
<li>LVS节点无法访问VIP的6443端口</li>
<li>RS节点可以访问VIP的6443端口，但是只会访问本机的服务，不会走负载均衡</li>
</ol>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">DR模式相关</span></div>
<div class="blog_h3"><span class="graybg">连接卡在SYN_RECV</span></div>
<p>可能原因是：</p>
<ol>
<li>RS没有配置虚IP</li>
<li>RS上的服务没有监听虚IP所在的网络接口</li>
</ol>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/ipvs-and-keepalived">IPVS和Keepalived</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/ipvs-and-keepalived/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>HAProxy知识集锦</title>
		<link>https://blog.gmem.cc/haproxy-faq</link>
		<comments>https://blog.gmem.cc/haproxy-faq#comments</comments>
		<pubDate>Sun, 25 Sep 2016 06:51:17 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[LB]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=22727</guid>
		<description><![CDATA[<p>简介 HAProxy是一个非常快、稳定的网络工具，可以用于实现高可用、负载均衡、L4/L7代理。经过多年的发展，HAProxy已经成为开源LB事实上的标准。 HAProxy的性能依赖于Linux的epool或BSD的kqueue。它包含了一个单线程、事件驱动、非阻塞的核心引擎、一个非常快的IO层，以及一个基于优先级的调度器。 HAProxy启动后，它会做三件事情： 处理入站连接 周期性的检查上游服务器的状态（健康检查） 和其它HAProxy节点交换信息 最近版本引入的新特性包括： 1.8：多线程、HTTP/2、缓存、服务器热添加/移除、无缝Reload、DNS SRV、硬件SSL引擎 1.7：服务器热配置、内容处理代理、多类型证书 1.6：DNS解析、HTTP连接多路复用、无状态压缩 1.5：SSL、IPv6、keep-alive、DDos防护</p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/haproxy-faq">HAProxy知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>HAProxy是一个非常<span style="background-color: #c0c0c0;">快、稳定</span>的网络工具，可以用于实现<span style="background-color: #c0c0c0;">高可用</span>、<span style="background-color: #c0c0c0;">负载均衡</span>、<span style="background-color: #c0c0c0;">L4/L7代理</span>。经过多年的发展，HAProxy已经成为开源LB事实上的标准。</p>
<p>HAProxy的性能依赖于Linux的epool或BSD的kqueue。它包含了一个单线程、事件驱动、非阻塞的核心引擎、一个非常快的IO层，以及一个基于优先级的调度器。</p>
<p>HAProxy启动后，它会做三件事情：</p>
<ol>
<li>处理入站连接</li>
<li>周期性的检查上游服务器的状态（健康检查）</li>
<li>和其它HAProxy节点交换信息</li>
</ol>
<p>最近版本引入的新特性包括：</p>
<p style="text-align: left; padding-left: 30px;">1.8：多线程、HTTP/2、缓存、服务器热添加/移除、无缝Reload、DNS SRV、硬件SSL引擎</p>
<p style="text-align: left; padding-left: 30px;">1.7：服务器热配置、内容处理代理、多类型证书</p>
<p style="text-align: left; padding-left: 30px;">1.6：DNS解析、HTTP连接多路复用、无状态压缩</p>
<p style="text-align: left; padding-left: 30px;">1.5：SSL、IPv6、keep-alive、DDos防护</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/haproxy-faq">HAProxy知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/haproxy-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Nginx知识集锦</title>
		<link>https://blog.gmem.cc/nginx-faq</link>
		<comments>https://blog.gmem.cc/nginx-faq#comments</comments>
		<pubDate>Sat, 09 Jan 2016 16:07:10 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[LB]]></category>
		<category><![CDATA[Nginx]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=17534</guid>
		<description><![CDATA[<p>Nginx简介 Nginx读作Engine X，在被广泛的用作： HTTP服务器 反向代理服务器 邮件代理服务器 通用的TCP/UDP代理 Nginx服务由一个Master进程和多个Worker进程构成。Master进程的主要职责是读取、解析配置文件，并维护工作进程。工作进程则负责实际的请求处理。为了高效的在Worker之间分发请求，Nginx引入了依赖于操作系统的、高效的事件驱动模型。Worker进程的数量常常根据CPU核心数设置。 对比Apache2   Apache2 Nginx Web服务器 适合处理动态请求 稳定，功能强 更强大的rewrite 超多的模块 更少的资源消耗：10000非活跃Keep-Alive连接仅仅消耗2.5M内存 更多的并发连接，理论上不受限制，取决于内存，10W没问题 静态处理性能比Apache2高3倍 简单，效率高 热部署：Master管理进程和Worker工作进程分离，可以在不停机的前提下升级二进制文件、修改配置、更换日志文件 <a class="read-more" href="https://blog.gmem.cc/nginx-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/nginx-faq">Nginx知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">Nginx简介</span></div>
<p>Nginx读作Engine X，在被广泛的用作：</p>
<ol>
<li style="font-size: 14px;">HTTP服务器</li>
<li>反向代理服务器</li>
<li>邮件代理服务器</li>
<li>通用的TCP/UDP代理</li>
</ol>
<p>Nginx服务由一个Master进程和多个Worker进程构成。Master进程的主要职责是读取、解析配置文件，并维护工作进程。工作进程则负责实际的请求处理。为了高效的在Worker之间分发请求，Nginx引入了依赖于操作系统的、高效的事件驱动模型。Worker进程的数量常常根据CPU核心数设置。</p>
<div class="blog_h2"><span class="graybg">对比Apache2</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;"> </td>
<td style="text-align: center;">Apache2</td>
<td style="text-align: center;">Nginx</td>
</tr>
</thead>
<tbody>
<tr>
<td>Web服务器</td>
<td>
<p><span style="color: #1a1a1a;">适合处理动态请求</span></p>
<p>稳定，功能强</p>
<p>更强大的<span style="color: #1a1a1a;">rewrite</span></p>
<p>超多的模块</p>
</td>
<td>
<p>更少的资源消耗：10000非活跃Keep-Alive连接仅仅消耗2.5M内存</p>
<p>更多的并发连接，理论上不受限制，取决于内存，10W没问题</p>
<p>静态处理性能比Apache2高3倍</p>
<p>简单，效率高</p>
<p>热部署：Master管理进程和Worker工作进程分离，可以在不停机的前提下升级二进制文件、修改配置、更换日志文件</p>
</td>
</tr>
<tr>
<td><span style="color: #1a1a1a;">负载均衡器</span></td>
<td>通常不作为NLB</td>
<td>
<p><span style="color: #1a1a1a;">作为反向代理抗住并发 —— 异步模型</span></p>
</td>
</tr>
<tr>
<td><span style="color: #1a1a1a;">通信模型</span></td>
<td><span style="color: #1a1a1a;">同步多进程模型，一个连接对应一个进程</span></td>
<td><span style="color: #1a1a1a;">异步模型，多个连接（万级别）可以对应一个进程 </span></td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">安装</span></div>
<div class="blog_h2"><span class="graybg">安装</span></div>
<pre class="crayon-plain-tag">sudo apt-get install libpcre3 libpcre3-dev
sudo apt-get install zlib1g zlib1g.dev
sudo apt-get install openssl libssl-dev

wget https://nginx.org/download/nginx-1.13.8.tar.gz
tar xzf nginx-1.13.8.tar.gz  &amp;&amp; rm nginx-1.13.8.tar.gz 
cd nginx-1.13.8/
./configure --prefix=/usr
make -j8
sudo make install</pre>
<div class="blog_h3"><span class="graybg">configure参数</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">参数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>prefix</td>
<td>安装前缀</td>
</tr>
<tr>
<td>sbin-path</td>
<td>二进制文件安装前缀，默认$prefix/sbin/nginx</td>
</tr>
<tr>
<td>conf-path</td>
<td>Nginx主配置文件位置，默认$prefix/conf/nginx.conf.</td>
</tr>
<tr>
<td>pid-path</td>
<td>PID文件位置</td>
</tr>
<tr>
<td>error-log-path</td>
<td>主错误日志位置，默认$prefix/logs/error.log</td>
</tr>
<tr>
<td>http-log-path</td>
<td>主请求日志位置，默认$prefix/logs/access.log</td>
</tr>
<tr>
<td>user<br />group</td>
<td>Nginx工作进程的用户、组</td>
</tr>
<tr>
<td>with-*_module<br />without-*_module</td>
<td>
<p>启用/禁用特定模块</p>
<p>--with-stream 启用第四层代理/LB，即ngx_stream_core_module模块</p>
</td>
</tr>
<tr>
<td>with-*</td>
<td>从指定位置寻找依赖库基础</td>
</tr>
<tr>
<td>add-module</td>
<td>安装第三方模块到Nginx的二进制文件中</td>
</tr>
<tr>
<td>add-dynamic-module</td>
<td>指定动态模块目录</td>
</tr>
</tbody>
</table>
<p>配置示例：</p>
<pre class="crayon-plain-tag">./configure
    --sbin-path=/usr/bin/nginx
    --conf-path=/etc/nginx/nginx.conf
    --pid-path=/var/run/nginx/nginx.pid
    --with-http_ssl_module
    --with-pcre=../pcre-8.41
    --with-zlib=../zlib-1.2.11</pre>
<p>如果前缀设置为/usr，则主要路径如下：</p>
<pre class="crayon-plain-tag">nginx path prefix: "/usr"
nginx binary file: "/usr/sbin/nginx"
nginx modules path: "/usr/modules"
nginx configuration prefix: "/usr/conf"
nginx configuration file: "/usr/conf/nginx.conf"
nginx pid file: "/usr/logs/nginx.pid"
nginx error log file: "/usr/logs/error.log"
nginx http access log file: "/usr/logs/access.log" </pre>
<div class="blog_h2"><span class="graybg">启动</span></div>
<pre class="crayon-plain-tag"># nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]
#  -V            : 显示版本和构建配置选项，然后退出
#  -t            : 测试配置然后退出
#  -T            : 测试并输出配置，然后退出
#  -q            : 测试配置时抑制非错误类信息
#  -s signal     : 向Master进程发送信号：
#                     stop     快速关闭
#                     quit     优雅关闭
#                     reopen   重新打开日志文件
#                     reload   重新载入配置文件
#  -c filename   :指定配置文件
#  -g directives : 设置全局指令


# 启动Nginx并在后台运行
sudo nginx
# 检查服务器是否成功启动
curl http://localhost:80


# 停止服务器
sudo nginx -s stop</pre>
<div class="blog_h2"><span class="graybg">添加模块</span></div>
<div class="blog_h3"><span class="graybg">安装模块</span></div>
<p>可以在编译Nginx期间，把模块编译进来：</p>
<pre class="crayon-plain-tag"># 目录/path/to/echo-nginx-module中存放echo模块的源代码
./configure --prefix=/opt/nginx \ --add-module=/path/to/echo-nginx-module</pre>
<div class="blog_h3"><span class="graybg">动态模块</span></div>
<p>从1.9.11版本开始，Nginx支持动态模块。在编译Nginx时，你可以指定动态模块的存放目录： </p>
<pre class="crayon-plain-tag">./configure --add-dynamic-module=/usr/local/nginx/modules</pre>
<p>需要添加动态模块时，首先独立编译模块，然后在Nginx配置文件中指定：</p>
<pre class="crayon-plain-tag"># 加载动态模块
load_module /path/to/modules/ngx_http_echo_module.so;</pre>
<div class="blog_h1"><span class="graybg">理解配置文件</span></div>
<div class="blog_h2"><span class="graybg">配置文件结构</span></div>
<p>Nginx由很多模块组成，这些模块由控制文件中的指令（Directives）控制。指令可以分为：简单指令、块指令：</p>
<ol>
<li>简单指令格式：<pre class="crayon-plain-tag">directive-name param1 param2;</pre></li>
<li> 块指令格式：<br />
<pre class="crayon-plain-tag"># 跨指令体使用花括号包围
directive param1 {
    # 块指令内部可以包含其它指令
    directive {

    }
}</pre></p>
<p>块指令中可以包含其它指令，外部的指令称为上下文（Context）。不位于任何{}内的指令，可以认为是位于主上下文（Main Context）。例如events、http指令位于主上下文中，server可以位于http中，location可以位于server中</p>
</li>
<li>
<p>以#号开头的部分表示注释</p>
</li>
</ol>
<div class="blog_h2"><span class="graybg">变量</span></div>
<p>你可以在Nginx配置文件中读写变量，Nginx的变量只有一种类型 —— 字符串。</p>
<pre class="crayon-plain-tag"># 创建变量并赋值
set $a "hello world";
# 支持插值
set $b "$a, $a";
# 用花括号包围变量名，可以防止歧义
set $b "${first}world";

# 要使用$符号本身，可以使用ngx_geo模块的指令geo
geo $dollar {
    default "$";
}

server {
    location /test {
        # ngx_echo没看的echo指令可以将指定的内容输出为响应
        echo "This is a dollar sign: $dollar";
    }
}</pre>
<p>注意：</p>
<ol>
<li><span style="background-color: #c0c0c0;">变量的作用域是全局性的</span>，所有Nginx配置共享之</li>
<li>每个<span style="background-color: #c0c0c0;">HTTP请求都具有任何变量的独立副本</span>，类似于线程本地变量</li>
<li>变量的创建发生在Nginx配置文件加载阶段，赋值则发生在实际请求处理阶段</li>
<li>即使发生<span style="background-color: #c0c0c0;">内部跳转</span>（即跳转到不同location处理），变量仍然是<span style="background-color: #c0c0c0;">同一副本</span></li>
</ol>
<p>多个Nginx模块提供了预定义变量。其中<span style="background-color: #c0c0c0;">很多预定义变量都是只读</span>的，尝试修改会导致意外的后果。</p>
<div class="blog_h3"><span class="graybg">存取处理程序</span></div>
<p>在读写变量时，Nginx会执行一小段代码，分别称为读处理程序（get handler）、写处理程序（set handler）。不同的模块可能为它们的变量准备不同的处理程序，从而影响变量读写的行为。</p>
<p>ngx_map模块提供的map指令，可以设置用户定义变量之间的映射关系，并且自动缓存结果：</p>
<pre class="crayon-plain-tag"># 相当于为$foo变量注册取处理程序
# 该变量的默认值是0，如果$args=debug则该变量的值为1
# 仅仅在需要取$foo的值时，下面的指令才被计算，且一旦计算结果就被缓存，请求处理期间不会重新计算
map $args $foo {
    default     0;
    debug       1;
}</pre>
<div class="blog_h3"><span class="graybg">变量和父子请求</span></div>
<p>Nginx变量的生命周期是和请求绑定的。在Nginx中存在两种请求：</p>
<ol>
<li>主请求：由HTTP客户端发给Nginx的请求</li>
<li>子请求：Nginx在处理主请求时，内部发起的请求。这些请求类似于HTTP请求但是不牵涉任何网络通信。任何主请求/子请求都可以串行、并行发起多个子请求，甚至递归的向自己发起子请求</li>
</ol>
<p>子请求的示例：</p>
<pre class="crayon-plain-tag">http {
    server {
        listen 80;
        location /var {
            # 发起两个子请求
            echo_location /hello;
            echo_location /world "p1=v1&amp;p2=v2";
        }
        location /hello {
            echo Greetings;
        }
        location /world {
            echo World;
        }
    }
}</pre>
<p>关于父子请求中的变量，需要注意：</p>
<ol>
<li><span style="background-color: #c0c0c0;">当前请求（不管是主、子请求）都具有变量的独立副本</span>，通常不会相互干扰</li>
<li>大部分预定义变量都具有针对当前请求的副本。一些例外情况包括：$request_method总是返回主请求的HTTP方法</li>
</ol>
<p>注意子请求和内部跳转（rewrite指令可以引发）不同，后者不会产生新的变量副本。</p>
<p>ngx_echo模块的echo_location可以产生子请求，ngx_auth_request模块也可以产生子请求。ngx_auth_request比较特殊的地方是共享父请求的变量。</p>
<div class="blog_h2"><span class="graybg">指令执行阶段</span></div>
<p>Nginx配置文件虽然类似于编程语言，但是它在整体上是声明性的，而非过程性的。</p>
<p>处理每一个用户请求时，Nginx都是<span style="background-color: #c0c0c0;">按照若干个不同阶段（phase）依次处理</span>的，一般的<span style="background-color: #c0c0c0;">配置指令仅仅会注册并运行在某一个阶段</span>。这些阶段一共有11个。</p>
<p>即使在同一个阶段内，也不能对不同指令的执行顺序进行假设（通常先加载的模块，其指令先执行）。例如more_set_input_headers、rewrite_by_lua都在rewrite阶段的尾部执行，你不能假设其中哪个会先执行。</p>
<div class="blog_h3"><span class="graybg">post-read</span></div>
<p>于Nginx读取并解析完请求头后执行此阶段。模块ngx_realip的指令 set_real_ip_from、real_ip_header运行在此阶段：</p>
<pre class="crayon-plain-tag">http {
    server {
        listen 80;
        # 如果请求来自于本机，则改写其$remote_addr。支持CIDR
        set_real_ip_from 127.0.0.1;
        # 该指令可以指定多次
        set_real_ip_from 127.0.0.0/24;
        # 将$remote_addr改写为自定义头中的值
        real_ip_header   X-Original-Addr;
        location /test {
            # rewrite阶段位于post-read之后，因此这里读取到的是篡改后的$remote_addr
            set $addr $remote_addr;
            echo "from: $addr";
        }
    }
}

# curl -H 'X-Original-Addr: 8.8.8.8' $url
# 输出：
# from: 8.8.8.8</pre>
<div class="blog_h3"><span class="graybg">server-rewrite</span></div>
<p>当ngx_rewrite模块的指令配置在server块中时，则这些指令在此阶段执行。</p>
<div class="blog_h3"><span class="graybg">find-config</span></div>
<p>不支持Nginx模块在此阶段注册处理程序（指令）。在此阶段，Nginx 核心完成当前请求与 location 配置块之间的配对工作。 </p>
<p>在此阶段后，location中的指令才可能生效。</p>
<div class="blog_h3"><span class="graybg">rewrite</span></div>
<p>这个阶段的配置指令一般用来<span style="background-color: #c0c0c0;">对当前请求进行各种修改</span>，例如修改URL或者请求参数。运行在该阶段的指令例如：set、set_by_lua、rewrite_by_lua等。</p>
<p>ngx_set_misc模块、ngx_encrypted_session模块提供的指令、 set_by_lua指令都在此阶段执行，且可以ngx_rewrite模块提供的指令（例如set、rewrite）混合使用，不需要担心执行顺序的问题。</p>
<p>rewrite_by_lua总是在rewrite阶段的最后执行。</p>
<div class="blog_h3"><span class="graybg"> post-rewrite </span></div>
<p>不支持Nginx模块在此阶段注册处理程序（指令）。在此阶段，Nginx 核心完成rewrite阶段所声明的<span style="background-color: #c0c0c0;">内部跳转</span>操作。</p>
<div class="blog_h3"><span class="graybg">preaccess</span></div>
<p>标准模块ngx_limit_req、ngx_limit_zone运行在此阶段，前者控制访问的频度，后者控制访问的并发度。</p>
<div class="blog_h3"><span class="graybg">access</span></div>
<p>这个阶段的配置指令一般用于进行访问控制，例如检查用户访问权限、检查来源IP地址。运行在该阶段的指令例如：allow、deny、ngx_auth_request、access_by_lua等。</p>
<p>标准模块ngx_access提供的allow和deny指令可以控制IP地址的访问权限：</p>
<pre class="crayon-plain-tag">location /hello {
    # ngx_access模块的指令按照配置顺序执行，遇到第一条满足条件的allow/deny指令就不再检查后续的allow/deny
    allow 169.200.179.4/24;
    allow 127.0.0.1;
    deny all;

    echo "hello world";
}</pre>
<p>ngx_lua模块提供的指令access_by_lua，在此阶段的最后执行，可以在allow/deny指令检查之后执行更加复杂的验证逻辑。示例：</p>
<pre class="crayon-plain-tag">location /hello {
    access_by_lua '
        # 使用ngx.var前缀来访问Nginx变量
        if ngx.var.remote_addr == "127.0.0.1" then
            return
        end

        ngx.exit(403)
    ';
}</pre>
<div class="blog_h3"><span class="graybg">post-access</span></div>
<p>配合access阶段，实现ngx_http_core模块的 satisfy 指令的功能。如果在access阶段注册了多个处理程序，则satisfy可以取值为：</p>
<ol>
<li>all 必须所有处理程序都验证通过</li>
<li>any 只需要一个处理程序验证通过</li>
</ol>
<div class="blog_h3"><span class="graybg"> try-files<br /></span></div>
<p>这个阶段实现try_files指令的功能。</p>
<div class="blog_h3"><span class="graybg">content</span></div>
<p>这个阶段的配置指令负责响应内容的生成。运行在该阶段的指令例如：echo、content_by_lua、proxy_pass等。来自不同模块的content阶段指令通常不能声明在同一个location中。</p>
<p>echo指令支持调用多次，而content_by_lua则仅仅支持调用一次，这些细节由具体模块规定。</p>
<p>ngx_echo模块提供的指令echo_before_body、echo_after_body可以和其它运行在content阶段的指令协同工作，因为它工作在Nginx的输出过滤器（output filter，不属于11个请求处理阶段的特殊阶段）中。</p>
<p>如果一个location中没有<span style="background-color: #c0c0c0;">任何content阶段指令，则Nginx把请求映射到静态资源服务模块</span>。通常Nginx会配置三个静态资源服务模块，按照在content阶段的执行顺序，依次是ngx_index、ngx_autoindex、ngx_static。其中ngx_index、ngx_autoindex仅仅会处理以 / 结尾的URL，而ngx_static则相反，处理非/结尾的URL。</p>
<p>ngx_index主要用于在文件系统中自动查找首页文件，例如：</p>
<pre class="crayon-plain-tag">location / {
    # 自动在此目录下，依次寻找index.htm、index.html文件。如果找不到，由下一个content阶段指令处理
    root /var/www/;
    index index.htm index.html;
}</pre>
<div class="blog_h1"><span class="graybg">基础配置块</span></div>
<pre class="crayon-plain-tag"># 在前台运行
daemon off;
# 运行Nginx的用户
user  nobody;
# 工作进程数量
worker_processes  1;

# 错误日志的位置（相对于Prefix）
# error_log file [level];
# 日志级别：debug, info, notice, warn, error, crit, alert, emerg
# 其中，要使用debug，则必须以--with-debug构建Nginx
error_log  logs/error.log;
error_log  logs/error.log  notice;
# 将日志输出到标准错误
error_log stderr debug;

# PID文件位置
pid        logs/nginx.pid;


events {
    # 每个工作进程的最大客户端连接数
    # HTTP服务最大连接数 worker_processes * worker_connections
    # 反向代理最大连接数 worker_processes * worker_connections / 4
    worker_connections  1024;
    # 在接收到新连接通知后，让工作进程尽可能接受多的连接请求
    multi_accept on;
    # 明确指定连接处理方法
    # epoll为Linux 2.6+的高效方式；kqueue为FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0, macOS的高效方式
    use epoll;
} </pre>
<div class="blog_h1"><span class="graybg">第七层配置</span></div>
<p>作为HTTP服务器使用时，Nginx具有以下主要特性：</p>
<ol>
<li>支持静态文件、自动索引、文件描述符缓存</li>
<li>支持缓存的反向代理、负载均衡、容错</li>
<li>支持缓存FastCGI, uwsgi, SCGI</li>
<li>模块化架构。支持gzipping、byte ranges、chunked responses、XSLT、SSI（Server Side Includes，服务器端包含）、图像转换等过滤器。单个页面中的多个SSI可以被并行的包含进来</li>
<li>SSL和TLS支持</li>
<li>支持HTTP/2</li>
</ol>
<p>其它特性包括：</p>
<ol>
<li>基于名称或者IP的虚拟服务器</li>
<li>Keep-alive和管道化连接（pipelined connections）支持</li>
<li>日志：定制格式、缓冲写入、快速轮换、syslog支持</li>
<li>3xx-5xx错误码重定向</li>
<li>URL重写，支持基于正则式的重写</li>
<li>根据客户端地址执行不同函数</li>
<li>基于客户端地址、密码、子请求结果的身份验证</li>
<li>验证HTTP referer</li>
<li>支持PUT, DELETE, MKCOL, COPY, MOVE等HTTP方法</li>
<li>FLV和MP4流媒体支持</li>
<li>响应限速</li>
<li>限制同一IP地址的并发连接数、请求数</li>
<li>基于IP地址获取地理位置信息</li>
<li>支持A/B测试</li>
<li>请求镜像</li>
<li>内嵌Perl</li>
<li>nginScript，一个JavaScript子集语言</li>
</ol>
<div class="blog_h2"><span class="graybg">Web服务器配置</span></div>
<div class="blog_h3"><span class="graybg">HTTP配置</span></div>
<pre class="crayon-plain-tag">http {
    # 在此处包含其它配置文件，mime.types包含了MIME类型和扩展名之间的映射关系
    include       mime.types;
    # 如果不匹配任何MIME类型，使用下面的默认值
    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 path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
    # access_log off;
    # 指定访问日志位置和格式
    access_log  logs/access.log  main;
    # 访问日志可以输出到标准输出
    access_log /dev/stdout;


    # 这个将为打开文件指定缓存，默认是没有启用的，max 指定缓存数量，
    # 建议和打开文件数一致，inactive 是指经过多长时间文件没被请求后删除缓存。
    open_file_cache max=204800 inactive=20s;
    # 在上一条配置的inactive时间段内，文件被使用的最少次数，不小于该次数，则认为是active
    open_file_cache_min_uses 1;
    # 多长时间检查一次缓存的有效信息
    open_file_cache_valid 30s;


    # 是否启用基于内核态完成的文件描述符之间的数据拷贝
    sendfile        on;


    # 是否启用SOCKET选项 TCP_CORK
    tcp_nopush     on;
    # 启用TCP_NODELAY，即禁用Nagle算法，适用于低延迟需求、小数据量场景
    tcp_nodelay on;
    # 客户端连接的保活时间
    keepalive_timeout  65;

    # 启用GZIP压缩，默认关闭，开启可以节约流量，但是消耗CPU
    gzip  on;
    # 插入Vary: Accept-Encoding响应头
    gzip_vary on;
    # 根据指定正则式匹配的UA，禁用压缩
    gzip_disable "MSIE [1-6]\.(?!.*SV1)";
    # 启用GZIP的MIME类型
    gzip_types text/plain application/x-javascript text/css application/xml;

    # 启用服务器端包含
    ssi on;
    # 抑制SSI处理出错时输出的[an error occurred while processing the directive]信息
    ssi_silent_errors on;
    # 在text/html的基础上，附加的需要处理SSI命令的MIME类型
    ssi_types text/shtml;

    server {
        # 服务器名称 + 监听端口唯一的确定一个服务器
        # listen 127.0.0.1:8000;
        # listen 127.0.0.1;
        # listen 8000;
        # listen *:8000;
        # listen localhost:8000;
        listen       80;
        # 根据虚拟服务器（Virtual Server）的定义，Nginx会比对请求Host头和下面的配置项，选择第一个匹配的server
        # 比对按照如下优先级进行
        # 1、全限定的静态server_name
        # 2、前导通配，例如*.gmem.cc形式的server_name
        # 3、后缀通配，例如www.gmem.*形式的server_name配置
        # 4、正则式定义的server_name
        # 如果没有任何server.server_name匹配，则按下面的规则Fallback：
        # 1、寻找标记为默认服务器的server
        # 2、寻找第一个listen和请求端口匹配的server
        server_name  localhost;

        # 用于设置响应头Content-Type
        charset utf-8;

        # 为本服务器指定访问日志
        access_log  logs/host.access.log  main;

        # URL到文件系统的映射
        location / {
            # / 映射到html目录
            # 指定请求的根目录
            root   html;
            # Index文件列表
            index  index.html index.htm;
        }

        # 定义错误页面
        error_page  404              /404.html;
        error_page   500 502 503 504  /50x.html;

        # try_files file ... uri; 或者 try_files file ... =code;
        # 逐个判断文件是否存在，使用第一个找到的文件来处理请求
        # file的完整路径依赖于root和alias
        # 如果要检查目录的存在性，需要用 / 结尾，例如$uri/
        # 如果所有文件都不存在，则向uri发起一个内部跳转
        location /images/ {
            # 如果请求的URI不存在，则使用默认图片代替
            try_files $uri /images/default.gif;
        }
        # Wordpress伪静态配置
        try_files $uri $uri/ /index.php?$args;

        # URL可以精确到文件
        location = /50x.html {
            root   html;
        }

        location /i/ {
            # 请求/i/top.gif则返回/data/w3/images/top.gif这个文件
            # alias指令为指定的location定义一个代替的位置
            # alias必须以 / 结尾，且只能用于location块内部
            alias /data/w3/images/;
        }
    }
}</pre>
<div class="blog_h3"><span class="graybg">HTTPS配置</span></div>
<pre class="crayon-plain-tag">http {
    server {
        listen       443 ssl;
        server_name  localhost;
        # 数字证书
        ssl_certificate      cert.pem;
        # 私钥
        ssl_certificate_key  cert.key;

        # SSL会话缓存时间和超时时间
        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;

        # 声明服务器支持的用于建立安全连接的密码算法
        ssl_ciphers  HIGH:!aNULL:!MD5;
        # 优先使用服务器端的密码算法
        ssl_prefer_server_ciphers  on;

        location / {
            root   html;
            index  index.html index.htm;
        }
    }

}</pre>
<div class="blog_h3"><span class="graybg">预定义变量</span></div>
<p>模块ngx_http_core定义了以下变量：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>$uri</td>
<td>
<p>经过解码，不包含请求参数的URL</p>
<p>当你请求http://localhost:8080/media/时$uri为/media/</p>
</td>
</tr>
<tr>
<td>$request_uri</td>
<td>原始URL，未经解码</td>
</tr>
<tr>
<td>$args</td>
<td>请求的URL参数部分，支持写：<pre class="crayon-plain-tag">set $args "a=3&amp;b=4";</pre></td>
</tr>
<tr>
<td>$arg_XXX</td>
<td>名字为XXX（大小写不敏感）的未经解码的URL参数</td>
</tr>
<tr>
<td>$cookie_XXX</td>
<td>名字为XXX的Cookie</td>
</tr>
<tr>
<td>$http_XXX</td>
<td>名字为XXX的请求头</td>
</tr>
<tr>
<td>$sent_http_XXX</td>
<td>名字为XXX的响应头</td>
</tr>
<tr>
<td>$request_method</td>
<td>请求使用的HTTP方法</td>
</tr>
<tr>
<td>$remote_addr</td>
<td>请求客户端的IP地址</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">location</span></div>
<p>到底哪个location匹配请求，优先级如下:</p>
<ol>
<li>检查具有 = 前缀的location，如果找到匹配，停止搜索</li>
<li>检查具有 ^~ 前缀的location，如果找到匹配，停止搜索</li>
<li>按照声明顺序检查 ~ ~*前缀的location，如果多个匹配，选取正则式最长的那个</li>
<li>常规匹配，也就是URL前缀匹配</li>
</ol>
<p>各种Pattern的说明如下：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">模式</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>location = /uri</td>
<td>= 表示精确匹配，只有完全匹配上才能生效</td>
</tr>
<tr>
<td>location ^~ /uri</td>
<td>^~ 开头对URL路径进行前缀匹配，并且优先于正则式匹配</td>
</tr>
<tr>
<td>location ~ pattern</td>
<td>表示区分大小写的正则匹配</td>
</tr>
<tr>
<td>location ~* pattern</td>
<td>表示不区分大小写的正则匹配</td>
</tr>
<tr>
<td>location /uri</td>
<td>不带任何修饰符，也表示前缀匹配，但是优先级比正则式低</td>
</tr>
<tr>
<td>location /</td>
<td>默认匹配，任何未匹配到其它location的请求都会匹配到这里</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">access_log</span></div>
<p>日志格式中可用的变量如下表：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>$remote_addr $http_x_forwarded_for</td>
<td>记录客户端IP地址</td>
</tr>
<tr>
<td>$remote_user</td>
<td>记录客户端用户名称</td>
</tr>
<tr>
<td>$request</td>
<td>记录请求的URI和HTTP协议</td>
</tr>
<tr>
<td>$status</td>
<td>记录请求状态</td>
</tr>
<tr>
<td>$body_bytes_sent</td>
<td>发送给客户端的字节数，不包括响应头的大小</td>
</tr>
<tr>
<td>$bytes_sent</td>
<td>发送给客户端的总字节数</td>
</tr>
<tr>
<td>$connection</td>
<td>连接的序列号</td>
</tr>
<tr>
<td>$connection_requests</td>
<td>当前通过一个连接获得的请求数量</td>
</tr>
<tr>
<td>$msec</td>
<td>日志写入时间。单位为秒，精度是毫秒</td>
</tr>
<tr>
<td>$pipe</td>
<td>如果请求是通过HTTP流水线发送</td>
</tr>
<tr>
<td>$http_referer</td>
<td>记录从哪个页面链接访问过来的</td>
</tr>
<tr>
<td>$http_user_agent</td>
<td>记录客户端浏览器相关信息</td>
</tr>
<tr>
<td>$request_length</td>
<td>请求的长度（包括请求行，请求头和请求正文）</td>
</tr>
<tr>
<td>$request_time</td>
<td>请求处理时间，单位为秒，精度毫秒</td>
</tr>
<tr>
<td>$time_iso8601</td>
<td>ISO8601标准格式下的本地时间</td>
</tr>
<tr>
<td>$time_local</td>
<td>记录访问时间与时区</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">反向代理</span></div>
<p>反向代理功能主要由模块ngx_http_proxy_module提供。反向代理的很多指令以在http块中直接声明。</p>
<div class="blog_h3"><span class="graybg">简单代理</span></div>
<pre class="crayon-plain-tag">http {
    server {

        # 反向代理，默认所有请求转发给 8080端口处理
        location / {
            proxy_pass http://localhost:8080;
        }

        # 反向代理，将PHP请求转发给8000端口
        location ~ \.php {
            proxy_pass http://127.0.0.1:8000;
        }

        # gif/jpg/png文件不走反向代理
        # =  前缀表示精确匹配
        # ~  前缀表示大小写敏感的正则式匹配
        # ~* 前缀表示大小写不敏感的正则式匹配
        location ~ \.(gif|jpg|png)$ {
            root /data/images;
        }

        # 联用Rewrite
        # http://localhost/v2/xxx 重写为 http://localhost/v2/google_containers/xxx
        # 然后转发给 https://gcr.azk8s.cn/v2/google_containers/xxx
        location /v2/ {
            rewrite /v2/(.*) /v2/google_containers/$1 break;
            proxy_pass https://gcr.azk8s.cn;
            proxy_set_header Host gcr.azk8s.cn;
        }
    }

}</pre>
<div class="blog_h3"><span class="graybg">非Web服务器</span></div>
<p>要将请求转发给非HTTP的被代理服务器，选择适当的***_pass指令：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">指令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>fastcgi_pass</td>
<td>将请求转发给FastCGI服务器</td>
</tr>
<tr>
<td>uwsgi_pass</td>
<td>将请求转发给uwsgi服务器</td>
</tr>
<tr>
<td>scgi_pass</td>
<td>将请求转发给SCGI服务器</td>
</tr>
<tr>
<td>memcached_pass</td>
<td>将请求转发给Memcached服务器</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">传递请求头</span></div>
<p>默认情况下Nginx会修改两个请求头：Host、Connection并清除值为空的请求头，然后再转发给被代理服务器。</p>
<p>Host默认被设置为$proxy_host变量，Connection默认被设置为Close。你可以改变这些默认行为：</p>
<pre class="crayon-plain-tag">location /some/path/ {
    # 改写、添加转发给被代理服务器的请求头
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    # X-Forwarded-Fo不是标准请求头
    # 格式：X-Forwarded-For: client1, proxy1, proxy2，第一个是真实客户端IP，后续的是经过的代理
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    # 要阻止某个请求头被转发，将其设置为空
    proxy_set_header Accept-Encoding "";

    proxy_pass http://localhost:8000;
}</pre>
<div class="blog_h3"><span class="graybg">传递响应头</span></div>
<p>默认情况下Nginx不会把Date、Server、X-Pad、X-Accel-*响应头转发给客户端，使用proxy_hide_header指令可以指定额外的需要隐藏的响应头。示例：</p>
<pre class="crayon-plain-tag">proxy_hide_header X-Powered-By;</pre>
<div class="blog_h3"><span class="graybg">缓冲配置</span></div>
<p>默认情况下，Nginx接收被代理服务器的响应并放入内部缓冲，直到整个响应都接收到了才发送给客户端。这可以避免同步传输时因为客户端网速太慢，而拖累被代理服务器。</p>
<pre class="crayon-plain-tag">location / {
    # 为单个请求分配的缓冲的大小和数量
    proxy_buffers 16 4k;
    # 被代理服务器返回的第一部分被存放在下面的缓冲中
    proxy_buffer_size 2k;
    proxy_pass http://localhost:8000;
}</pre>
<p>如果禁用缓冲，则响应被同步的发送给客户端。在某些交互式应用场景下可能需要禁用缓冲以获得最快响应时间：</p>
<pre class="crayon-plain-tag">location / {
    proxy_buffering off;
    proxy_pass http://localhost:8000;
}</pre>
<div class="blog_h3"><span class="graybg">选择出口IP</span></div>
<p>如果Nginx有多重网络可以到达被代理服务器，你可能需要明确指定使用哪个网络接口：</p>
<pre class="crayon-plain-tag">location /app1/ {
    # 通过127.0.0.1向被代理服务器转发请求
    proxy_bind 127.0.0.1;
    proxy_pass http://gmem.cc/app1/;
}

location /app2/ {
    # 变量$server_addr为接收原始请求的网络接口的IP地址
    proxy_bind $server_addr;
    proxy_pass http://gmem.cc/app2/;
} </pre>
<div class="blog_h3"><span class="graybg">常用指令</span></div>
<pre class="crayon-plain-tag"># 代理的HTTP协议版本，默认1.0，如果需要使用Keepalive则设置为1.1
proxy_http_version 1.1;
# 连接到被代理服务器的超时时间
proxy_connect_timeout 50;
# 从被代理服务器读取响应的超时时间，仅仅用于两次连续的读操作，而不是整个响应内容的传输所消耗的时间 
proxy_read_timeout 20;
# 向被代理服务器发送请求的超时时间，仅仅用于两次连续的写操作，如果在此时间内被代理服务器每有接收任何数据，则关闭连接
proxy_send_timeout 20;
# 是否缓冲来自被代理服务器的响应
proxy_buffering on;
# 响应的一部分缓冲缓冲的大小，
proxy_buffer_size 32k;
# 缓冲区数量和大小
proxy_buffers 4 32k;
# busy_buffers是缓冲的一部分，通常设置为单个缓冲区的2倍大小
# 这部分缓冲用于存放向客户端返回的响应数据，满了则写入到临时文件
proxy_busy_buffers_size 64k;
# 临时文件（硬盘缓冲）的大小，默认是内存缓冲总大小的2倍，设置为0则关闭硬盘缓冲
proxy_temp_file_write_size 256k;
# 用于修改被代理服务器的响应的location头
proxy_redirect ~^http://10.0.0.1:8080(.*) http://zircon.gmem.cc$1;
proxy_redirect off;
# 设置proxy_hide_header、proxy_set_header指令使用的哈希表的大小
proxy_headers_hash_max_size 51200;
proxy_headers_hash_bucket_size 6400;</pre>
<div class="blog_h3"><span class="graybg">预定义变量</span></div>
<p>模块ngx_http_proxy_module定义了以下变量：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>$proxy_host</td>
<td>被代理服务器的名字和端口</td>
</tr>
<tr>
<td>$proxy_port</td>
<td>被代理服务器的端口</td>
</tr>
<tr>
<td>$proxy_add_x_forwarded_for</td>
<td>客户端的请求头X-Forwarded-For后缀以$remote_addr，如果请求头没有X-Forwarded-For字段则该变量等于$remote_addr</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">静态缓存</span></div>
<p>模块ngx_http_proxy_module提供了基础的缓存功能：</p>
<pre class="crayon-plain-tag"># /path/to/cache/ 用于缓存的本地磁盘目录
# levels=1:2 设置一个两级层次结构的目录，大量的文件放置在单个目录中会导致访问缓慢，推荐使用两级目录
# keys_zone 设置一个共享内存区，该内存区用于存储缓存键和元数据。可以让Nginx不需要读取磁盘即确定缓存HIT or MISS
#     10MB 为共享内存区大小，平均1MB可以存放8000键
# max_size=10g 缓存占用磁盘的最大空间，如果不指定则无限增长
# inactive=60m 文件多长时间不被访问，则从内存移除
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m
# 默认情况下，Nginx先把缓存写到一个临时区域，然后再拷贝到缓存目录
# 建议关闭临时区域，避免不必要的拷贝
use_temp_path=off;

server {
    location / {
        # 此命令启动匹配此location的URL的缓存，其键存放到my_cache
        proxy_cache my_cache;
        # 被代理的上游服务器
        proxy_pass http://my_upstream;
        # 无法从原始服务器获取最新的内容时，Nginx 可以分发缓存中的过期（Stale）数据给客户端
        # 当上游服务器返回错误、超时、50X状态码时，即使缓存陈旧了，仍然使用
        # 所谓陈旧，依据上游服务器响应中设置的过期时间
        proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
    }

}</pre>
<p>你可以把缓存文件分发到多个磁盘上：</p>
<pre class="crayon-plain-tag">proxy_cache_path /path/to/hdd1 levels=1:2 keys_zone=my_cache_hdd1:10m max_size=10g;
proxy_cache_path /path/to/hdd2 levels=1:2 keys_zone=my_cache_hdd2:10m max_size=10g;
split_clients $request_uri $my_cache {
    # 两个缓存区域分别负担一半缓存文件
    50% "my_cache_hdd1";
    50% "my_cache_hdd2";
}

server {
    location / {
        proxy_cache $my_cache;
    }
}</pre>
<div class="blog_h2"><span class="graybg">正向代理</span></div>
<p>Nginx本身不支持正向代理，可以使用第三方模块：<a href="https://github.com/chobits/ngx_http_proxy_connect_module">ngx_http_proxy_connect_module</a>。</p>
<p>下载模块源码后，需要对Nginx本身源码进行patch：</p>
<pre class="crayon-plain-tag"># 注意选择和Nginx版本匹配的patch
patch -p1 &lt;  /root/CPP/tools/ngx_http_proxy_connect_module/patch/proxy_connect_rewrite_1018.patch
./configure  --add-module=/root/CPP/tools/ngx_http_proxy_connect_module
make</pre>
<p>下面是将Nginx作为一个HTTPS代理的配置示例：</p>
<pre class="crayon-plain-tag">http {
    server {
        listen 8088 ssl;
        resolver 8.8.8.8 ipv6=off;
        server_name proxy.gmem.cc;

        ssl_certificate_key /etc/nginx/certs/proxy.gmem.cc.key;
        ssl_certificate /etc/nginx/certs/proxy.gmem.cc_bundle.pem;

        proxy_connect;
        proxy_connect_allow            all;
        proxy_connect_connect_timeout  10s;
        proxy_connect_read_timeout     10s;
        proxy_connect_send_timeout     10s;
    }
}</pre>
<div class="blog_h2"><span class="graybg">负载均衡</span></div>
<p>模块ngx_http_upstream_module为Nginx提供了负载均衡的支持。使用该模块，你可以定义一个服务器组，并在proxy_pass, fastcgi_pass, uwsgi_pass, scgi_pass,memcached_pass等指令中引用这些组。</p>
<div class="blog_h3"><span class="graybg">upstream</span></div>
<p>定义一个服务器组，服务器可以监听不同的端口，甚至可以监听UNIX域套接字。</p>
<p>默认情况下，请求会<span style="background-color: #c0c0c0;">根据权重，使用round-robin方式在组内各服务器之间分发</span>。如果和一个<span style="background-color: #c0c0c0;">服务器通信失败，会转而尝试下一个服务器</span>，如果所有服务器都不能获得正确响应，则最后一个服务器的结果被发送给客户端。</p>
<pre class="crayon-plain-tag">resolver 10.96.0.10;

upstream backend {
    # upstream指令中可以包含多个server指令，其格式为：server address [parameters];
    # 此服务器的权重为5
    server backend1.example.com       weight=5;
    # 此服务器的权重为默认值1，30s内最多失败3次，最多允许100个同时的连接
    server 127.0.0.1:8080       max_fails=3 fail_timeout=30s max_conns=100;
    server unix:/tmp/backend3;
    # 此服务器为备份服务器，仅当主服务器不可用时，请求才发送到备份服务器
    server backup1.example.com:8080   backup;
    # 此服务器被标记为永久不可用
    server down.example.com           down;
    # 访问DNS服务器，监控服务器域名对应的IP地址，自动更新服务器对应的IP地址
    # 必须在http块中添加resolver指令才能生效
    # slow_start，当服务从不健康变为健康、不可用变为可用时，其权重恢复正常值所需要的时间
    # drain，此模式下的服务器，仅仅bound到该服务器的请求才发送给它
    # route，上游服务器的route名，用于会话绑定
    server media-api.dev.svc.k8s.gmem.cc resolve slow_start=10 drain route=string;

    # 指定存放动态配置的组的配置信息的文件
    state /var/lib/nginx/state/servers.conf;

    # 缓存到上游服务器的TCP连接，每个工作进程缓存32个连接，如果超过此数量则使用LRU算法清除
    # 需要配合proxy_http_version 1.1使用
    keepalive 32;

    # 如果处理请求时，不能立刻选取适用的上游服务器，可以让请求排队
    # 如果排队满了，或者超时，则返回502给客户端
    queue number [timeout=time];

    # 定义一个名为name，大小为size的共享内存区域。此区域存放此服务器组的配置、运行时状态
    # 所有Worker进程共享此内存区域。多个服务器组可以共享单个区域（name相同），这种情况下size只需要声明一次
    zone name [size];
}</pre>
<div class="blog_h3"><span class="graybg">负载均衡算法</span></div>
<p>ngx_http_upstream_module支持多种负载均衡算法，这些算法对应不同的指令。所有指令均必须位于upstream块中：</p>
<pre class="crayon-plain-tag"># 负载均衡算法：客户端和上游服务器的对应关系依赖于key的哈希值
# key可以包含文本、变量，或者两者的组合
# 默认情况下不使用一致性哈希，添加服务器节点后可能导致大量的key重新映射到不同服务器
# 如果指定consistent参数，则采用一致性哈希算法。添加服务器后，仅少量key重新映射，可以保证缓存服务器的命中率
hash key [consistent];

# 负载均衡算法：基于客户端的IP地址来映射请求到上游服务器
# 可以保证来自一个客户端的请求，总是由同一服务器处理，除非目标服务器不可用
ip_hash;

# 负载即均衡算法：将请求发送给具有最少活动连接数的上游服务器，同时考虑服务器的权重
least_conn;

# 负载均衡算法：将请求转发给具有最短响应时间+最少活动连接数的服务器，同时考虑服务器的权重
# 格式：least_time header | last_byte [inflight];
# header以收到请求头时间计，last_byte以收到完整响应时间计
least_time;</pre>
<div class="blog_h3"><span class="graybg">sticky</span></div>
<p>该指令用于实现会话绑定（session affinity），必须位于upstream块中。指令格式：</p>
<pre class="crayon-plain-tag"># 基于Cookie的绑定，会话绑定到的目标服务器信息，由Nginx自动产生的Cookie传递
# 对于尚未绑定到特定服务器的请求，Nginx会使用负载均衡算法为其选取一个上游服务器，并种植Cookie
sticky cookie name [expires=time] [domain=domain] [httponly] [secure] [path=path];
# 示例：
sticky cookie srv_id expires=1h domain=.example.com path=/;

# 基于Route的绑定
# 被代理服务器第一次接收到客户端请求时，会为客户端分配一个route信息，此客户端的所有后续请求均在
# Cookie或者URI中附带route信息。此信息和server的route参数进行比较，从而确定哪个服务器负
# 责处理请求
# 如果没有为server指定route参数，则其route为IP和端口的MD5的HEX形式
sticky route $variable ...;
# 示例：
map $cookie_jsessionid $route_cookie {
    ~.+\.(?P\w+)$ $route;
}
map $request_uri $route_uri {
    ~jsessionid=.+\.(?P\w+)$ $route;
}
upstream backend {
    server backend1.example.com route=a;
    server backend2.example.com route=b;
    # 优先从Cookie JSESSIONID中获取route信息，如果没有则从URI参数jsessionid中获取
    sticky route $route_cookie $route_uri;
}


# 由Nginx来分析上游服务器的响应，从中学习到服务器初始化的会话（通常以Cookie的形式传递给客户端）
# create，lookup分别用于获取服务器创建的会话ID、客户端传递的会话ID。这两个指令都可以声明多次
# 第一个不为空的变量生效
# 会话和上游服务器的对应关系，被存放在共享内存区域zone中。1MB的共享内存在64bit机器上可以存放8000会话
# 连续timeout没有被访问的会话，自动重共享内存区域中清除，默认timeout=10分钟
# header用于从上游服务器接收到指定的响应头后，创建一个session
sticky learn create=$variable lookup=$variable zone=name:size [timeout=time] [header];
# 示例：
upstream backend {
   server backend1.example.com:8080;
   server backend2.example.com:8081;

   sticky learn
          # 上游服务器将会话ID存放在Cookie EXAMPLECOOKIE中
          create=$upstream_cookie_examplecookie
          # 读取客户端请求的Cookie EXAMPLECOOKIE来确定其上游服务器
          lookup=$cookie_examplecookie
          # 会话-服务器对应关系存放在名为client_essions的共享内存中
          zone=client_sessions:1m;
}</pre>
<div class="blog_h3"><span class="graybg">预定义变量</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>$upstream_addr</td>
<td>
<p>上游服务器的地址端口。如果处理请求过程中牵涉到多个上游服务器，则用逗号分隔，例如</p>
<p style="padding-left: 30px;">192.168.1.1:80, 192.168.1.2:80, unix:/tmp/sock</p>
<p>如果发生跨越多组服务器的内部跳转，则不同组的服务器用分号分隔，例如</p>
<p style="padding-left: 30px;">192.168.1.2:80, unix:/tmp/sock : 192.168.10.1:80</p>
</td>
</tr>
<tr>
<td>$upstream_bytes_received</td>
<td>从上游服务器接收到的字节数</td>
</tr>
<tr>
<td>$upstream_cache_status</td>
<td>存放响应缓存的状态，取值：MISS、BYPASS、EXPIRED、STALE、UPDATING、REVALIDATED、HIT</td>
</tr>
<tr>
<td>$upstream_connect_time</td>
<td>与上游服务器创建连接所消耗的时间</td>
</tr>
<tr>
<td>$upstream_cookie_name</td>
<td>上游服务器通过Set-Cookie响应头种植的名为name的Cookie的值</td>
</tr>
<tr>
<td>$upstream_header_time</td>
<td>上游服务器通过响应头设置的时间</td>
</tr>
<tr>
<td>$upstream_response_length</td>
<td>上游服务器的响应长度</td>
</tr>
<tr>
<td>$upstream_response_time</td>
<td>接收上游服务器响应所消耗的时间</td>
</tr>
<tr>
<td>$upstream_status</td>
<td>上游服务器的响应状态码</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">完整示例</span></div>
<p>下面是一个L7负载均衡配合SSL Termination的例子：</p>
<pre class="crayon-plain-tag">http {
    upstream ceph-dashboard {
        server ceph-1.gmem.cc:8443;
        server ceph-2.gmem.cc:8443 backup;
    }
    server {
        listen                443 ssl;
        server_name           ceph.gmem.cc;
        ssl_certificate       /etc/ssl/gmem.cc/fullchain.pem;
        ssl_certificate_key   /etc/ssl/gmem.cc/privkey.pem;
        location / {
            proxy_pass https://ceph-dashboard;
            proxy_ssl_verify off;
        }
    }
} </pre>
<div class="blog_h2"><span class="graybg">URL重写</span></div>
<p>模块ngx_http_rewrite_module提供了URL重写功能。该模块的指令的执行顺序如下：</p>
<ol>
<li>位于server块的指令依次顺序执行</li>
<li>循环执行：
<ol>
<li>根据请求URL找到匹配的location</li>
<li>顺序执行location块中的指令</li>
<li>如果URL被重写，则返回到2.1重复执行，但是最多不超过10次</li>
</ol>
</li>
</ol>
<div class="blog_h3"><span class="graybg">break</span></div>
<p>停止继续为当前请求处理ngx_http_rewrite_module模块的指令。</p>
<div class="blog_h3"><span class="graybg">rewrite</span></div>
<p>执行URL重写：<pre class="crayon-plain-tag">rewrite regex replacement [flag];</pre></p>
<p><span style="background-color: #c0c0c0;">如果regex匹配请求URL（去除http://host:port剩余的部分）</span>，则将URL改写为replacement。rewrite指令按照其声明的顺序依次执行。flag可以是：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">flag</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>last</td>
<td>停止处理当前location中的ngx_http_rewrite_module指令。并且，如果URL被改写了，寻找匹配的新location进行处理</td>
</tr>
<tr>
<td>break</td>
<td>停止处理当前location中的ngx_http_rewrite_module指令。行为类似于break指令</td>
</tr>
<tr>
<td>redirect</td>
<td>
<p>使用302状态码返回一个临时的重定向（24-48小时之内临时发生的网页转移），replacement必须以http://、https://或$scheme开头</p>
</td>
</tr>
<tr>
<td>permanent</td>
<td>使用301状态码返回永久重定向</td>
</tr>
</tbody>
</table>
<p>如果replacement以http://、https://或$scheme开头，则表示进行重定向。示例：</p>
<pre class="crayon-plain-tag">http {
    # 永久重定向
    server {
        listen       80;
        rewrite / https://blog.gmem.cc permanent;
    }
}</pre>
<div class="blog_h3"><span class="graybg">if</span></div>
<p>在满足条件的情况下，执行花括号内的指令：<pre class="crayon-plain-tag">if (condition) { ... }</pre></p>
<p>应当仅仅在花括号内使用return、rewrite、last等rewrite指令，否则可能导致意外行为，甚至出现段错误SIGSEGV。</p>
<p>其中condition可以是以下之一：</p>
<ol>
<li>$变量名，如果变量值为0或空串则condition为false</li>
<li>基于 <pre class="crayon-plain-tag">=</pre> 或 <pre class="crayon-plain-tag">!=</pre> 操作符比较变量和字符串</li>
<li>基于 <pre class="crayon-plain-tag">~</pre> 或 <pre class="crayon-plain-tag">*~</pre>操作符（以及<pre class="crayon-plain-tag">!~</pre>或<pre class="crayon-plain-tag">!~*</pre>）来匹配变量和正则式，*~表示大小写不敏感。正则式中可以包含捕获，并在随后通过$1..$9引用</li>
<li>基于 <pre class="crayon-plain-tag">-f</pre> 或 <pre class="crayon-plain-tag">!-f</pre> 操作符检查文件是否存在</li>
<li>基于 <pre class="crayon-plain-tag">-x</pre> 或 <pre class="crayon-plain-tag">!-x</pre> 操作符检查可执行文件是否存在</li>
<li>基于 <pre class="crayon-plain-tag">-d</pre> 或 <pre class="crayon-plain-tag">!-d</pre> 操作符检查目录是否存在</li>
<li>基于 <pre class="crayon-plain-tag">-e</pre> 或 <pre class="crayon-plain-tag">!-e</pre> 操作符检查目录、文件、符号连接是否存在</li>
</ol>
<p>示例：</p>
<pre class="crayon-plain-tag"># 如果UA为IE
if ($http_user_agent ~ MSIE) {
    # 则将URL前缀以/msie
    rewrite ^(.*)$ /msie/$1 break;
    # 可以重写为外部URL
    rewrite ^(.*)$ https://nginx.gmem.cc/grafana/avatar.png;
}

# 如果Cookie中有id则设置变量
if ($http_cookie ~* "id=([^;]+)(?:;|$)") {
    set $id $1;
}
# 如果HTTP方法为POST则返回405状态码
if ($request_method = POST) {
    return 405;
}
# 如果缓慢则限速
if ($slow) {
    limit_rate 10k;
}

# 根据扩展名来设置缓存过期时间
location ~* \.(js|css|jpg|jpeg|gif|png|swf)$ {
    # 如果文件存在，则设置过期时间为1小时
    if (-f $request_filename) {
        expires 1h;
        break;
    }
}

# 防盗链
location ~* \.(gif|jpg|swf)$ {
    valid_referers none blocked www.gmem.cc;
    if ($invalid_referer) {
       rewrite ^/ http://$host/logo.png;
    }
}</pre>
<div class="blog_h3"><span class="graybg">return</span></div>
<p>停止处理并返回状态码给客户端，格式：</p>
<pre class="crayon-plain-tag">return code [text];
return code URL;
return URL;</pre>
<div class="blog_h3"><span class="graybg">rewrite_log </span></div>
<p>如果设置为on则记录URL重写日志</p>
<div class="blog_h2"><span class="graybg">TLS Termination</span></div>
<pre class="crayon-plain-tag">http {
    client_max_body_size 4g;
    server {
        listen                443 ssl;
        server_name           git.gmem.cc;
        ssl_certificate       /etc/letsencrypt/live/gmem.cc/fullchain.pem;
        ssl_certificate_key   /etc/letsencrypt/live/gmem.cc/privkey.pem;
        location / {
            proxy_pass https://127.0.0.1:2443;
            proxy_ssl_verify off;
        }
    }

    server {
        listen                443 ssl;
        server_name           harbor.gmem.cc;
        ssl_certificate       /etc/letsencrypt/live/gmem.cc/fullchain.pem;
        ssl_certificate_key   /etc/letsencrypt/live/gmem.cc/privkey.pem;
        client_max_body_size  0;
        location / {
            proxy_pass https://127.0.0.1:4443;
            proxy_ssl_verify off;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_buffering off;
            proxy_request_buffering off;
        }
    }
    server {
        listen                443 ssl default_server;
        ssl_certificate       /etc/letsencrypt/live/gmem.cc/fullchain.pem;
        ssl_certificate_key   /etc/letsencrypt/live/gmem.cc/privkey.pem;
        return 404;
    }
}</pre>
<div class="blog_h2"><span class="graybg">最佳实践</span></div>
<div class="blog_h3"><span class="graybg">root放到server</span></div>
<p>而不是放到location中，这样可以让所有location使用相同的root</p>
<div class="blog_h3"><span class="graybg">避免重复的index</span></div>
<pre class="crayon-plain-tag">http {
    index index.php index.htm index.html;
}</pre>
<div class="blog_h3"><span class="graybg">避免滥用if</span></div>
<p>要检查文件是否存在，使用try_files。if仅仅用于配合rewrite模块的指令使用。 </p>
<div class="blog_h3"><span class="graybg">避免代理一切</span></div>
<pre class="crayon-plain-tag">server {
    location / {
        # 先尝试本地静态文件，然后才发送给代理
        try_files $uri $uri/ @proxy;
    }
    location @proxy {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/tmp/phpcgi.socket;
    }
}</pre>
<div class="blog_h1"><span class="graybg">第四层配置</span></div>
<div class="blog_h2"><span class="graybg">简介</span></div>
<p>作为TCP/UDP代理服务器时，Ngin具有以下特性：</p>
<ol>
<li>可以作为通用的TCP/UDP代理</li>
<li>为TCP添加SSL和TLS SNI支持</li>
<li>负载均衡和容错</li>
<li>基于客户端地址的访问控制</li>
<li>根据客户端地址执行不同函数</li>
<li>限制同一IP地址的并发连接数</li>
<li>日志：定制格式、缓冲写入、快速轮换、syslog支持</li>
<li>基于IP地址获取地理位置信息</li>
<li>支持A/B测试</li>
<li>nginScript</li>
</ol>
<p>在1.9.0之前的版本，基于TCP的代理和负载均衡需要依赖第三方补丁nginx_tcp_proxy_module。</p>
<p>从1.9.0开始，可以在构建时<span style="background-color: #c0c0c0;">指定<pre class="crayon-plain-tag">--with-stream</pre>选项，启用</span>内置第四层代理/负载均衡。第四层代理的功能由ngx_stream_core_module模块提供。</p>
<div class="blog_h2"><span class="graybg">工作流程</span></div>
<p>Nginx处理客户端的TCP/UDP会话，由以下阶段组成。</p>
<div class="blog_h3"><span class="graybg">Post-accept</span></div>
<p>此阶段，Nginx刚刚接受了客户端连接。ngx_stream_realip_module模块参与到此阶段。</p>
<div class="blog_h3"><span class="graybg">Pre-access</span></div>
<p>访问预检查。ngx_stream_limit_conn_module 模块参与到此阶段。</p>
<div class="blog_h3"><span class="graybg">Access</span></div>
<p>访问权限检查。ngx_stream_access_module模块参与到此阶段。</p>
<div class="blog_h3"><span class="graybg">SSL</span></div>
<p>TLS/SSL termination。ngx_stream_ssl_module模块参与到此阶段。</p>
<div class="blog_h3"><span class="graybg">Preread</span></div>
<p>预读取，读取客户端数据的初始字节到预读取缓冲中。允许ngx_stream_ssl_preread_module等模块对数据进行分析。</p>
<div class="blog_h3"><span class="graybg">Content</span></div>
<p>实际处理数据的阶段，调用被代理服务器，或者返回一个值给客户端。</p>
<div class="blog_h3"><span class="graybg">Log</span></div>
<p>记录客户端会话的处理结果，ngx_stream_log_module模块参与此阶段。</p>
<div class="blog_h2"><span class="graybg">配置项解释</span></div>
<pre class="crayon-plain-tag"># 定义一个第四层代理
stream {

    # TCP_NODELAY
    tcp_nodelay on;

    upstream mysqld {
        hash $remote_addr consistent;
        server 192.168.1.42:3306 weight=5 max_fails=1 fail_timeout=10s;
        server 192.168.1.43:3306 weight=5 max_fails=1 fail_timeout=10s;
        # 可以通过UNIX Domain Socket连接上游
        server unix:/var/run/mysql.sock;
    }

    server {
        # 该指令可以指定多次
        #
        # listen address:port [ssl] [udp] [proxy_protocol] [backlog=number] [rcvbuf=size]
        #                       [sndbuf=size] [bind] [ipv6only=on|off] [reuseport]
        #                       [so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]];
        #
        # address:port 代理服务器的监听端口，可以指定 *:3306、10.0.0.1:3306等形式
        #
        # ssl 指定此端口上所有的连接必须工作在SSL模式下
        #
        # udp 指定此端口工作在UDP协议下
        #
        # proxy_protocol 指定此端口上的所有连接必须使用PROXY协议
        # 关于此协议，参考http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
        # PROXY 2 从1.13.11开始被支持
        #
        # backlog 最大排队等待被接受的连接数
        #
        # rcvbuf SO_RCVBUF
        # sndbuf SO_SNDBUF
        #
        # bind 提示针对每个给出的address:port，分别执行执行bind()调用
        #
        # ipv6only IPV6_V6ONLY
        # reuseport 基于SO_REUSEPORT选项，为每个Worker创建独立的监听套接字。内核将会分别入站请求
        #           给每个Worker，要求Linux 3.9+
        #
        # so_keepalive TCP保活选项：TCP_KEEPIDLE、TCP_KEEPINTVL、TCP_KEEPCNT
        #
        listen 3306;

        # 预读缓冲大小
        preread_buffer_size 16k;
        # 预读超时
        preread_timeout 30s;

        # 读取PROXY协议头的超时时间，如果超过此时间仍然没有传输完整的PROXY头则连接关闭
        proxy_protocol_timeout 30s;

        # 用来解析Upstream的DNS服务器，DNS的缓存有效期
        resolver 127.0.0.1 [::1]:5353 valid=30s;
        # DNS解析超时
        resolver_timeout 5s;

        proxy_connect_timeout 1s;
        proxy_timeout 3s;
        proxy_pass mysqld;

        # 来自模块ngx_stream_limit_conn_module的配置
        # limit_conn zone number;
        # 限制每个地址仅仅允许同时1个连接
        limit_conn addr 1;
        limit_conn_log_level error;
    }

}</pre>
<div class="blog_h2"><span class="graybg">变量列表</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">变量</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>$binary_remote_addr</td>
<td>二进制形式的客户端地址</td>
</tr>
<tr>
<td>$bytes_received</td>
<td>从客户端接收到的字节数</td>
</tr>
<tr>
<td>$bytes_sent</td>
<td>发送到客户端的字节数</td>
</tr>
<tr>
<td>$connection</td>
<td>连接串号</td>
</tr>
<tr>
<td>$hostname</td>
<td>主机名</td>
</tr>
<tr>
<td>$msec</td>
<td>当前时间，毫秒精度</td>
</tr>
<tr>
<td>$pid</td>
<td>当前工作进程PID</td>
</tr>
<tr>
<td>$protocol</td>
<td>当前使用的协议，TCP或UDP</td>
</tr>
<tr>
<td>$proxy_protocol_addr</td>
<td>PROXY协议头中的客户端地址</td>
</tr>
<tr>
<td>$proxy_protocol_port</td>
<td>PROXY协议头中的客户端端口</td>
</tr>
<tr>
<td>$remote_addr</td>
<td rowspan="2">客户端地址、端口</td>
</tr>
<tr>
<td>$remote_port</td>
</tr>
<tr>
<td>$server_addr</td>
<td rowspan="2">接受连接的服务地址、端口</td>
</tr>
<tr>
<td>$server_port</td>
</tr>
<tr>
<td>$session_time</td>
<td>会话持续时间，毫秒精度</td>
</tr>
<tr>
<td>$status</td>
<td>
<p>会话状态，取值：</p>
<p style="padding-left: 30px;">200 会话成功完成<br />400 客户端数据无法解析，例如PROXY头无法无法识别<br />403 访问被拒绝<br />500 内部服务器错误<br />502 网关失败，例如上游服务器无法访问<br />503 服务不可用，例如超过连接数限制</p>
</td>
</tr>
<tr>
<td>$time_iso8601 </td>
<td>ISO 8601格式的本地时间</td>
</tr>
<tr>
<td>$time_local</td>
<td>本地时间 </td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">配置示例</span></div>
<div class="blog_h3"><span class="graybg">SSH转发</span></div>
<pre class="crayon-plain-tag">stream {
    upstream ssh {
        hash $remote_addr consistent;
        server 192.168.1.42:22 weight=5;
    }

    server {
        listen 2222;
        proxy_pass ssh;
    }
}</pre>
<div class="blog_h3"><span class="graybg">MySQL负载均衡</span></div>
<pre class="crayon-plain-tag">stream {
    upstream mysqld {
        hash $remote_addr consistent;
        server 192.168.1.42:3306 weight=5 max_fails=1 fail_timeout=10s;
        server 192.168.1.43:3306 weight=5 max_fails=1 fail_timeout=10s;
    }

    server {
        listen 3306;
        proxy_connect_timeout 1s;
        proxy_timeout 3s;
        proxy_pass mysqld;
    }
} </pre>
<div class="blog_h3"><span class="graybg">SSL后端</span></div>
<p>需要额外编译配置项：<pre class="crayon-plain-tag">--with-stream_ssl_preread_module</pre>。示例：</p>
<pre class="crayon-plain-tag">daemon off;
worker_processes 4;
pid /var/run/nginx/nginx.pid;
worker_rlimit_nofile 74363;
worker_shutdown_timeout 10s ;
events {
    multi_accept        on;
    worker_connections  16384;
    use                 epoll;
}

stream {
    # 连接日志
    log_format log_stream '$remote_addr [$time_local] $protocol [$ssl_preread_server_name] [$ssl_preread_alpn_protocols] [$name] $status $bytes_sent $bytes_received $session_time';       
    access_log  /usr/logs/stream.log log_stream;
    map $ssl_preread_server_name $name {
        blog.gmem.cc apache;
        git.gmem.cc  gitea;
        default apache;
    }

    upstream apache {
        server 127.0.0.1:8443;
    }

    upstream gitea {
        server ::1:3443;
    }

    server {
        listen 0.0.0.0:443;
        proxy_pass $name;
        ssl_preread on;
    }
}</pre>
<div class="blog_h2"><span class="graybg">真实客户端IP</span></div>
<p>L4配置，上游服务器看到的客户端地址是127.0.0.1，要让上游服务器看到真实客户端IP，有几种方法。</p>
<div class="blog_h3"><span class="graybg">代理协议</span></div>
<p>这种方式，需要后端支持代理协议（作为协议的服务器端）。对于Apache，启用代理协议的方法参考<a href="https://blog.gmem.cc/apache-http-server-faq#proxy-protocol">Apache HTTP Server知识集锦</a>。</p>
<p>Nginx的配置如下：</p>
<pre class="crayon-plain-tag">stream {
    server {
        listen 0.0.0.0:443;
        proxy_protocol on;
        proxy_pass $name;
        ssl_preread on;
    }
}</pre>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">静态转发</span></div>
<p>在被代理服务中，设置响应头X-Accel-Redirect，可以提示Nginx进行静态转发，例如：</p>
<pre class="crayon-plain-tag">X-Accel-Redirect: /media/encryp//10/57/1497089.html</pre>
<p>Nginx配置：</p>
<pre class="crayon-plain-tag">server {
    root  /datastore/;
}</pre>
<p>则Nginx自动从 /datastore/media/encryp//10/57/1497089.html获取静态文件。</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/nginx-faq">Nginx知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/nginx-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
