870 likes | 1.16k Views
搭建 Linux Web 服务器. 李博杰 BOJIELI@GMAIL.COM LUG@USTC 2013-03-23. 目录. 最小 权限原则 配置 SSH Key 理解 HTTP 配置 nginx 配置 php -fpm 配置 MySQL 性能测试、监控与优化 数据备份. 最小权限原则. 如果 Web 程序报 Permission Denied 错误 …… 网上教程通常是让把目录权限设成 777 有些 Web 程序为了方便安装,要求把上传目录权限改成 777 假设我们需要在服务器上运行一个自己编写的服务 ……
E N D
搭建Linux Web服务器 李博杰BOJIELI@GMAIL.COM LUG@USTC 2013-03-23
目录 • 最小权限原则 • 配置SSH Key • 理解HTTP • 配置nginx • 配置php-fpm • 配置MySQL • 性能测试、监控与优化 • 数据备份
最小权限原则 • 如果Web程序报Permission Denied错误…… • 网上教程通常是让把目录权限设成777 • 有些Web程序为了方便安装,要求把上传目录权限改成777 假设我们需要在服务器上运行一个自己编写的服务…… • 把数据放在/var/lib里,以root身份运行? • 把数据放在家目录里,以自己的身份运行? 善用Linux用户吧! • 新建一个用户和同名的组,以这个用户的身份运行 • 把代码和数据的owner和group改成这个用户 • 目录权限775,文件权限664 • 将需要修改代码/数据的其他用户加入这个组
SSH是如何实现加密的 • 古人使用的加密技术都是对称加密,加密和解密使用同一个密钥,如何保证可靠地传递密钥呢? • SSH背后的技术是RSA,一种非对称加密算法 • 用公钥加密,用私钥解密 • 公钥可以发布出去,让对方把要传输的数据用公钥加密,自己再用私钥解密 • 通信双方分别拥有一对公私钥,就能在不安全的信道上进行双向数据传输了 • 中间人攻击:我想做你们的透明代理…… • ~/.ssh/known_hosts里存储了每个SSH过的计算机的公钥
配置SSH Key • 有多少人每次登录服务器都要输入密码? • 用ssh-keygen吧! • 生成的private key默认在~/.ssh/id_rsa,千万不要泄露哦 • 生成的public key默认在~/.ssh/id_rsa.pub,可以发布出去 • 将public key上传到服务器的~/.ssh/authorized_keys:ssh remote-host “mkdir –p ~/.ssh”ssh remote-host “chmod 700 ~/.ssh”scp ~/.ssh/id_rsa.pub remote-host:~/.ssh/authorized_keys • 如果你还在用root登录…… • adduser, sudo, visudo, /etc/group的基本操作一定要会哦
HyperText Transfer Protocol 浏览器 服务器 TCP SYN 接受连接 TCP ACK GET / HTTP/1.1 准备 / 的内容 HTTP/1.1 200 OK TCP ACK 浏览器解析 GET /jquery.js HTTP/1.1 HTTP/1.1 200 OK
HTTP Request • $ curl -v mirrors.ustc.edu.cn> GET / HTTP/1.1> User-Agent: curl/7.29.0> Host: mirrors.ustc.edu.cn> Accept: */*>< HTTP/1.1 200 OK< Server: nginx/0.7.67< Date: Thu, 21 Mar 2013 04:55:48 GMT< Content-Type: text/html< Content-Length: 14457< Last-Modified: Thu, 21 Mar 2013 04:55:02 GMT< Connection: keep-alive< Accept-Ranges: bytes 请求行 客户端类型 访问哪个主机 接受的响应类型 空行表示请求头结束 响应行 服务器类型 时间 内容类型 内容(不含头)长度 用于缓存 是否保持TCP连接 支持断点续传 Request Header Response Header
HTTP请求行 • 格式:请求类型路径 HTTP/1.1 • 请求类型包括GET POST HEAD等 • GET用于“只读”请求 • POST用于有修改作用的请求 • HEAD只返回response header • 路径以/开头,一般格式为/path/to/file?param1=value1¶m2=value2&option • HTTP请求中,\r\n表示换行
HTTP响应行 • 格式:HTTP/1.1 状态码 状态解释 • 200 OK • 206 Partial Content 断点续传 • 301 Moved Permanently 永久重定向 • 302 Found 临时重定向 • 304 Not Modified 用缓存吧 • 400 Bad Request 请求格式错误 • 403 Forbidden • 404 Not Found • 500 Internal Server Error 2xx成功 3xx重定向 4xx请求错误 5xx服务器错误
Bashttpd • 读请求行,获取请求类型、URI和HTTP版本号 • 依次匹配bashttpd.conf中的各条URI规则,一旦匹配上就执行对应的操作 • 以serve_dir_or_file_from操作为例: • 过滤非法路径 • 如果要读的是目录且有index.html,则输出之 • 如果要读的是目录且没有index.html,则调用ls • 如果要读的是文件,则获取文件类型和长度放进Response Header,并输出内容 • 如果读取失败,返回403;如果文件没找到,返回404
Linux Web服务器组件 PHP MySQL Nginx Linux内核 网络协议栈
Linux服务器请求过程 MySQL 浏览器 Nginx PHP 建立TCP连接 分配HTTP请求 执行脚本 数据库查询 执行脚本 数据库查询 执行脚本 接受PHP输出 返回给用户 浏览器渲染
Apache进程模型 主进程 子进程 子进程 子进程 子进程 Web服务在线程“内部解决”,如mod_php模块。每个线程负责一个PHP请求。 线程 线程 线程 问题:每个请求的处理过程中始终占着一个线程,上下文切换频繁,难以支持高并发。 客户端 客户端 客户端
Nginx为什么比Apache性能高 • select vs. epoll • Apache使用select系统调用,轮询所有连接看有没有新动态,没有则小睡一会再次轮询(每次轮询还得拷贝所有文件描述符到内核空间) • Nginx使用epoll系统调用,让内核保存需要监视的文件描述符,有新动态时就调用回调函数,不需要轮询 • 内部解决 vs. 任务外包 • Apache的处理模块在内部,看起来降低了进程间通信的开销,事实上使很多无关资源不能及时得到释放 • Nginx术业有专攻,把PHP之类的都“外包”给CGI,每个进程可以同时处理很多请求,节约资源
Nginx的异步处理机制 • Nginx比起Apache的“大包大揽”,只是一个轻量级的HTTP反向代理服务器。 • 空闲worker进程“争着”接收客户端的HTTP请求,谁接收到了,就根据配置文件规则分发到对应的处理模块(还记得bashttpd吗?) • 如果是文件,就通过sendfile,把发送文件的任务丢给内核 • 如果需要通过php-fpm等CGI,就异步发给CGI • 此时worker可以去接收新的HTTP请求 • CGI处理完返回时触发worker,worker发送给客户端
Nginx的异步处理机制 • 信号 • reload配置:kill –HUP `cat /var/run/nginx.pid` • logrotate:kill –USR1 `cat /var/run/nginx.pid` • 网络事件 • 各worker初始化时给监听的端口添加事件 • 如果有新请求,各worker在上锁的情况下争相accept,抢到的worker给这个连接初始化,添加读事件 • 如果有读事件,则根据这个连接目前的状态,决定解析请求行或请求头。两种解析分别是一个有限状态机,如果发现已读数据不够,则下次读事件再试。请求体由处理模块负责读取。 • 如果是超时事件,则结束对应的连接
CGI vs. FastCGI • 前面的bashttpd就像CGI,本身只能处理一个请求。每处理一个请求,nginx就得fork出一个cgi来。 • FastCGI则是一个CGI在生命周期内可以一个接一个地处理很多请求,节省了进程创建和初始化的开销。
Nginx配置文件结构 root 全局配置:error_log, worker_processes… events http server location location server https
Nginx配置作用域 • 内层配置覆盖外层配置 • Nginx官方文档中每个选项(Directive)都有适用范围(Context) • 例如keepalive_timeout适用于http, server, location,则location配置优先于server配置,server配置优先于http配置 • 在Debian中,/etc/nginx/nginx.conf的httpblock有: • include /etc/nginx/conf.d/*.conf • include /etc/nginx/sites-enabled/* • 一般每个server(虚拟主机)一个配置文件,放在sites-available目录,再符号链接到sites-enabled
Nginx处理请求的步骤 • 根据端口号和Host选择server • listen 80 default_server是什么意思? • server级别rewrite • 根据location匹配规则,选择第一个匹配到的location • location级别rewrite • 访问控制:allow, deny, auth_basic • 尝试文件:try_files • 显示内容:autoindex, fastcgi, gzip_static, uwsgi… • 记录日志:access_log
location directive • location [ = | ~ | ~* | ^~ ] uri { ... } • 前缀匹配 • location / 匹配所有URI(它一般是fallback) • location ^~ /images/ 匹配所有/images/开头的URI • 完整匹配 • location = /favicon.ico 只匹配/favicon.ico这个URI • 正则表达式匹配 • location ~* \.(gif|jpg|jpeg|png)$ 匹配图片文件(大小写不敏感) • location ~ documents 匹配含有documents的URI(大小写敏感)
location directive • 匹配顺序: • 完整匹配,如果匹配则停止 • 前缀匹配,如果找到的最长前缀匹配恰好用了^~修饰,则停止 • 正则表达式匹配,从上到下进行,只要找到匹配即停止 • 使用前缀匹配找到的最长前缀匹配
设置Web目录 • location /~boj/ { alias /home/boj/;} • 结果:/~boj/test.html => /home/boj/test.html • location /~boj/ {root /home/boj/;} • 结果:/~boj/test.html => /home/boj/~boj/test.html
设置Web目录 • location ~ ^/download/(.*)$ { alias /srv/www/static/$1;} • 结果:/download/test.html => /srv/www/static/test.html • location ~ ^/download/(.*)$ {root /srv/www/static/$1;} • 结果:/download/test.html => /srv/www/static/download/test.html
Nginx配置文件中可用变量 • GET /test.php?a=1&b=helloHost: lug.ustc.edu.cnUser-Agent: Mozilla/5.0
Nginx Rewrite • Rewrite,就是把一个URI换成另一个URI,重新走location定位过程。 • 为防止环路,一个请求的rewrite至多进行10次。 • break • 结束当前location块,不再执行后面的语句 • return • 直接返回错误状态码
Nginx Rewrite • if • 字符串相同比较:=, != • 正则表达式:~, ~*, !~, !~* • 文件存在:-f, !-f • 目录存在:-d, !-d • 文件或目录存在:-e !-e • 文件可执行:-x !-x • if语句中可以使用前面说的变量,例如 • if ($http_user_agent ~ MSIE) {…} • 建议使用try_files(使用第一个找到的文件)代替if: • try_files$uri $uri/ /index.html;
Nginx Rewrite • rewrite • last 结束当前rewrite的处理,但仍然执行非rewrite语句,重新调度 • break 不再执行当前location块中后续的rewrite,但仍然执行非rewrite语句 • 在location外,last与break等效 • 在location内一般用break,如果用last有可能重新进入此location,形成环路 • redirect 立即返回HTTP 302 • permanent 立即返回HTTP 301
Nginx Rewrite • rewrite "/photos/([0-9]{2})([0-9]{2})([0-9]{2})" /path/to/photos/$1/$1$2/$1$2$3.png; • 效果:/photos/130321 => /path/to/photos/13/1303/130321.png • 比较: • rewrite “/photos/([0-9]*)” /path/to/photos/$1.png; • rewrite “/photos/([0-9]*)” /path/to/photos/$1.png redirect;
Nginx proxy • location /linux/ {proxy_passhttp://202.38.70.187/;proxy_redirect default;} • Nginx将location中的部分(/linux/)替换成proxy_pass中指定的部分(http://202.38.70.187/)。 • proxy_redirect是对上游返回的response header中的Location重写(不然301/302跳转就出错了) • proxy_redirect default 相当于proxy_redirect http://202.38.70.187/ /linux/; • 前缀匹配前一个URI,替换成后一个URI
Nginx proxy • 使用UNIX socket做上游: • proxy_pass http://unix:/path/to/backend.socket:/uri/; • 默认不forward request Host,如果需要forward: • proxy_set_header Host $host; • 如果location是用正则表达式匹配的,或者URI被location内的rewrite规则作用过,则直接在proxy_pass参数后附加从“/”开始的整个URI: • location /backend-task/ { rewrite /backend-task/([^/]+) /task?query=$1 break;proxy_pass http://192.168.0.100;}
负载均衡 – 权重 • 按照weight随机分配到backend: • upstream backends { server backend1.example.com weight=5; server backend2.example.com:8080; #默认weight=1 server unix:/var/run/backend3.sock;} • server { location /static/ {proxy_pass http://backends; }}
负载均衡 – IP hash • 按照IP地址的hash值分配,可以保证同IP用户始终被分配到同一后端,便于处理session: • upstream backends{ip_hash; server backend1.example.com; server backend2.example.com:8080; server unix:/var/run/backend3.sock down;} • server { location /dynamic/ {proxy_pass http://backends; }}
设置错误页 • error_page 404 /404.html; • error_page 502 503 504 /50x.html; • error_page 403 http://example.com/forbidden.html; • error_page 404 = @fallback; • location @fallback { • proxy_passunix:/var/run/backend.sock; • }
Nginx Access Log • access_log path [format [buffer=size [flush=time]]] • format需在http中定义,默认为combined • log_format combined '$remote_addr - $remote_user [$time_local] ''"$request" $status $body_bytes_sent''"$http_referer" "$http_user_agent"'; • 除了前面提到的变量,还有一些可以log的统计量: • $request_length: 请求body的字节数 • $request_time: Nginx处理请求全过程所用毫秒数 • $connection: 连接的序列号
图片防盗链 • location ~* \.(jpg|gif|png|swf|flv|wma|wmv|asf|mp3|mmf|zip|rar)$ {valid_referrers none blocked *.example.com example.com; if ($invalid_referrer) { rewrite ^/ http://example.com/error.gif; }} • none表示没有Referer字段 • blocked表示Referer字段被防火墙mask
一些小问题 • 调整最大并发数: • connection:TCP连接,每个worker维护一个连接池 • 简单文件最大并发 = worker_connections * worker_processes • 反向代理服务器(fastcgi)最大并发 = worker_connections * worker_processes/ 2(由于nginx还要与fastcgi之间保持连接) • 上传大附件,出现413 Request Entity Too Large怎么办? • 调整请求body大小限制: • client_max_body_size 0(不限制)或100m
PHP-FPM是什么 Nginx PHP-FPM master worker1 worker2 worker3
为什么选用PHP-FPM • php-fpm是fastcgi的另一种实现。它好在哪里呢? • 支持pid file, log file, setsid, setuid, setgid, chroot • 重启php-fpm服务器时不会丢失请求 • 限制请求来源IP • 根据负载动态调整worker个数 • 使用不同的php.ini配置和uid/gid/chroot环境启动worker • 对php程序的输出做日志 • fastcgi_finish_request()可以结束输出,但脚本继续执行,可以执行长时间的任务而不损害用户体验
PHP请求过程 每个php-fpmworker只能同时处理一个PHP请求,也就是有多少个worker,就能并发处理多少个PHP请求。
PHP-FPM信号处理 • SIGINT, SIGTERM: 立即停止服务 • SIGQUIT: 服务完当前请求后停止服务(service stop) • SIGUSR1: 重新打开日志文件(logrotate) • SIGUSR2: 各worker服务完当前请求后重启,达到重新载入配置文件的目的(service reload)
Debianphp-fpm配置文件 • 在/etc/php5/fpm/目录下: • php.ini是与PHP语言相关配置 • conf.d目录是被php.ini include的,一般是PHP模块配置 • php-fpm.conf是与php-fpm进程池相关配置 • pool.d目录是被php-fpm.conf include的,每个文件是一个进程池,默认是www.conf这个进程池
php-fpm常用配置 • 监听socket文件而非TCP端口: • listen = /var/run/php-fpm/php-fpm.sock • worker进程的所有者: • user = www-data • group = www-data • dynamic进程管理策略下的worker进程数: • pm.max_children最大worker数 • pm.start_servers PHP-FPM启动时spawn的worker数 • pm.min_spare_servers worker数少于此值就会spawn • pm.max_spare_servers最大worker数
php-fpm常用配置 • 其他进程管理策略: • static:固定个数(pm.max_children)的worker • ondemand:有请求来时才spawn worker,每个worker空闲pm.process_idle_timeout就退出 • 如果第三方模块有内存泄漏…… • 每处理pm.max_requests个请求就重启worker • 事实上编写PHP扩展时,只要谨慎使用persistent resource就不会有内存泄漏,请求结束时emalloc的内存会被释放 • 查询php-fpm状态的Web接口:pm.status_path • 访问日志,参见www.conf的注释
php.ini配置 • PHP配置分为四种级别: • PHP_INI_SYSTEM 只能在php.ini中,或Apache的httpd.conf中配置 • PHP_INI_PERDIR 只能在php.ini或.user.ini中,或Apache的httpd.conf或.htaccess中配置 • PHP_INI_USER 可以在php程序中用ini_set函数配置 • PHP_INI_ALL = PHP_INI_PERDIR | PHP_INI_USER • 在php-fpm中,worker配置 • php_flag[display_errors]可以覆盖php.ini的display_errors项 • php_admin_flag[log_errors]可以覆盖log_errors项,且此项不再可以在php程序中用ini_set修改
php.ini常用配置 – 安全 • allow_url_fopen允许把URL当作文件名,用fopen打开 • allow_url_include允许把URL当作include的文件(危险) • disable_classes禁用类,用逗号隔开 • disable_functions禁用函数,用逗号隔开 • expose_php在reponse header中包括X-Powered-By信息 • open_basedir禁止访问basedir以外的文件 • safe_mode已经废弃,不要再用了
php.ini常用配置– 错误 • display_errors遇到PHP错误时,是否输出错误信息 • error_reporting错误报告级别,包括E_ERROR, E_WARNING, E_PARSE, E_NOTICE, E_STRICT, E_DEPRECATED等,一般设为E_ALL & ~E_NOTICE • html_errors以HTML形式输出PHP错误 • log_errors是否写错误日志
php.ini常用配置 – 资源限制 • max_execution_time最大执行的秒数 • max_input_vars $_GET, $_POST数组的最大元素数,用以避免hash冲突攻击 • post_max_size POST请求的大小限制,应当大于要上传的文件大小 • upload_max_filesize上传文件的大小限制 • max_file_uploads一个请求最多上传的文件数 • memory_limit内存限制,应当大于上传文件大小
php.ini常用配置 – 其他 • auto_prepend_file自动在PHP脚本前添加代码 • auto_append_file自动在PHP脚本后添加代码(如果脚本中调用了exit则不会执行append_file) • extension_dirPHP扩展的查找路径 • sendmail_path mail函数发送邮件的命令