Rootop 服务器运维与web架构

2020-01-07
发表者 Venus
排查redis占用内存达90%以上已关闭评论

排查redis占用内存达90%以上

帮别人排查一个问题,项目还没上线但redis占用内存很高。思路如下:

1、登陆redis控制台,首先用 keys * 获取所有的key

> keys *
x:x:a
x:x:b
x:x:c

发现key也就十来个,用 TYPE x:x:a 发现a是一个list数据类型

用lrange命令查看list中指定索引的值
用法: lrange key start end #获取列表中从 start 到 end 的值。
start 从0开始计,start、end也可为负数,倒数第一元素的位置为-1,倒数第二为-2,以此类推
lrange key 0 -1 # 第一个元素到倒数第一个元素(全部元素)

> lrange 0 1

发现值为json格式的数据

# 统计list长度,用 LLEN 命令可以返回列表的长度

> LLEN x:x:a

统计出 x:x:a 这个key有十几万多个索引

写了个脚本统计了下所有的key长度约370万,这样推测是数据量太大导致占用内存非常高。
经询问项目里有个模块用redis,个人推测要么用来做消息队列但没被消费,要么是做缓存,但是旧数据没有清理导致堆积越来越大。

PS:主要介绍了几个redis操作命令。

2019-12-28
发表者 Venus
关于iptables放行端口无效已关闭评论

关于iptables放行端口无效

之前在iptables配置时都是直接去修改配置文件,然后重启,这次在命令行添加放行端口发现端口还不通。

# 查看当前iptables netfilter表的INPUT链规则

[root@HKSRV2426 ~]# iptables -L -n
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:22 state NEW 
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:443 
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:5555 state NEW 
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           state RELATED,ESTABLISHED 
ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0           
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           state NEW tcp dpt:22 
REJECT     all  --  0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited 

# 比如放行21端口

[root@HKSRV2426 ~]# iptables -A INPUT  -p tcp --dport 21 -j ACCEPT

这么添加还是无法访问21端口。

# 查看规则其实是在第8条规则下面新增的。

[root@HKSRV2426 ~]# iptables -L -n --line-number
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination         
1    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:22 state NEW 
2    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:443 
3    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:5555 state NEW 
4    ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           state RELATED,ESTABLISHED 
5    ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0           
6    ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           
7    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           state NEW tcp dpt:22 
8    REJECT     all  --  0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited 
9    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           tcp dpt:21 

第8条规则已经REJECT拒绝包了,下面的规则就不需要再继续匹配了。
所以防火墙规则一般是允许的放在拒绝上面,最后为默认规则才是拒绝或者丢弃。

# 可以通过插入规则解决,比如在规则3上面插入一条

[root@HKSRV2426 ~]# iptables -I INPUT 3 -p tcp --dport 3306 -j ACCEPT

分析原因:
# 查看防火墙配置文件

[root@HKSRV2426 ~]# vi /etc/sysconfig/iptables
略·
-A INPUT -j REJECT --reject-with icmp-host-prohibited

上面这条规则就是上面查询到的第8条规则。

REJECT和DROP基本一样,区别在于它除了阻塞包之外, 还向发送者返回错误信息。

--reject-with # 设置返回错误信息种类 
icmp-host-prohibited # 种类

全部的种类可以通过下面命令查到。

[root@HKSRV2426 ~]# iptables -j REJECT --help
Valid reject types:
icmp-net-unreachable     	ICMP network unreachable
net-unreach              	alias
icmp-host-unreachable    	ICMP host unreachable
host-unreach             	alias
icmp-proto-unreachable   	ICMP protocol unreachable
proto-unreach            	alias
icmp-port-unreachable    	ICMP port unreachable (default)
port-unreach             	alias
icmp-net-prohibited      	ICMP network prohibited
net-prohib               	alias
icmp-host-prohibited     	ICMP host prohibited
host-prohib              	alias
tcp-reset                	TCP RST packet
tcp-rst                  	alias
icmp-admin-prohibited    	ICMP administratively prohibited (*)
admin-prohib             	alias

2019-12-27
发表者 Venus
phpstudy服务器面板已关闭评论

phpstudy服务器面板

phpstudy最早做本地php开发环境,现在出了服务器版。
phpstudy面板官方说是基于docker容器实现的,会创建一个容器用于运行面板服务。
此面板适用于不熟悉服务器操作的初学者用。

根据官方文档安装好后开始查看运行环境。

[root@VM_0_4_centos ~]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                   PORTS                  NAMES
8d3fcc05a239        centos_ssl:v1       "/bin/bash"         4 hours ago          Exited (0) 3 hours ago                          centos_ssl
23403cc60eb8        centos:v4           "/bin/bash"         4 hours ago          Up 4 hours                                      centos_env

发现有一个容器名为 centos_env 的在运行。

# 然后有个疑问

[root@VM_0_4_centos ~]# netstat -tnlp # 宿主机上执行

看到不少监听的进程而且有进程名,而不像以前用的docker都是docker-proxy监听的端口。
进入容器name为centos_env的容器,netstat也发现有同样的进程。这就表明了容器内的服务直接寄宿在宿主机上
也就意味着容器是跑在host网络模式下。这样容器内监听了什么地址,宿主机上直接就能看到,不需要桥接网络方式的映射端口。

docker网络有4中模式,分别为:

none模式,使用--net=none指定,该模式关闭了容器的网络功能。
host模式,使用--net=host指定,容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。
bridge模式,使用--net=bridge指定,默认设置 ,此模式会为每一个容器分配、设置IP等,并将容器连接到一个docker0虚拟网桥,通过docker0网桥以及Iptables nat表配置与宿主机通信。
container模式,使用--net=container:NAME_or_ID指定,创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围。

# 查phpstudy的安装脚本发现,运行了2个容器,都指定了host网络模式

docker create -it --name centos_env --network=host -v /usr/local/phpstudy:/usr/local/phpstudy -v /www:/www  centos:v4
docker create -it --name centos_ssl --network=host -v /usr/local/phpstudy:/usr/local/phpstudy -v /www:/www  centos_ssl:v1

# 运行一个自己的容器

[root@VM_0_4_centos ~]# docker run -dit --name test --net=bridge -p 8888:80 centos:v4
204d5854d4bd91167f33c54ef732adb58d11a1eea743a4a0f39374547f4026e3
docker: Error response from daemon: driver failed programming external connectivity on endpoint test (800abef340ef085a242ca05f98e2e6d8cbf400ea634dfd9ff05a96fd2f0a09a6):  (iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport 8888 -j DNAT --to-destination 172.18.0.2:80 ! -i docker0: iptables: No chain/target/match by that name.

提示找不到规则链

这个问题好一顿排查,之前都是直接yum安装docker,没遇到过此问题。

# 手动查nat表是没有DOCKER这条链的,目前还不知道链是由什么服务去生成的。

[root@VM_0_4_centos ~]# iptables -t nat -L -n | grep DOCKER
[root@VM_0_4_centos ~]# #没有返回信息

# 通过ps查看有两个关于docker服务的进程

[root@VM_0_4_centos ~]# ps aux | grep docker
root      6996  0.2  1.9 348624 35976 ?        Sl   14:14   0:00 dockerd
root      7000  0.1  0.9 375516 17532 ?        Ssl  14:14   0:00 containerd --config /var/run/docker/containerd/containerd.toml --log-level info

dockerd # docker守护进程 docker daemon
containerd # 容器虚拟化技术,从docker中剥离出来,形成开放容器接口(OCI)标准的一部分。

可以参考:https://www.cnblogs.com/embedded-linux/p/10850491.html

[root@VM_0_4_centos ~]# containerd --help | grep iptables
[root@VM_0_4_centos ~]# 
[root@VM_0_4_centos ~]# dockerd --help | grep iptables
      --iptables                                Enable addition of iptables rules (default true)

对这2个命令查帮助文档,只在dockerd中发现有关于iptables的信息。

现在试试对dockerd加上参数启动

[root@VM_0_4_centos ~]# ps aux | grep docker
root     14107  0.6  1.8 419964 34060 pts/0    Sl   14:21   0:07 dockerd
root     14112  0.0  0.9 376576 18764 ?        Ssl  14:21   0:00 containerd --config /var/run/docker/containerd/containerd.toml --log-level info
root     14577  0.0  0.2  11780  4484 ?        Sl   14:22   0:00 containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/287264f03a04ce0f30af6f0b8f73923ca563eba5862a09f6df179f978ac5dd25 -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/local/phpstudy/docker-18.09.6/containerd -runtime-root /var/run/docker/runtime-runc
root     18451  0.0  0.0 112708   976 pts/0    R+   14:40   0:00 grep --color=auto docker
[root@VM_0_4_centos ~]# kill -9 14107
[root@VM_0_4_centos ~]# docker ps
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
[root@VM_0_4_centos ~]# dockerd --iptables & 

# 再去查看是否有DOCKER这条链

[root@VM_0_4_centos ~]# iptables -t nat -L -n | grep DOCKER
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL
DOCKER     all  --  0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL
Chain DOCKER (2 references)

发现有了DOCKER链

# 再次创建容器并映射端口测试

[root@VM_0_4_centos ~]# docker run -dit --name test --net=bridge -p 8888:80 centos:v4
ef23d213b1685d4e4e1fb1b4a6d6b39d3ca3d2455b33d0de175e734ccb7e3d72
INFO[2019-12-20T14:54:54.889562578+08:00] shim containerd-shim started                  address="/containerd-shim/moby/ef23d213b1685d4e4e1fb1b4a6d6b39d3ca3d2455b33d0de175e734ccb7e3d72/shim.sock" debug=false pid=20626
[root@VM_0_4_centos ~]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
ef23d213b168        centos:v4           "/bin/bash"         4 seconds ago       Up 3 seconds        0.0.0.0:8888->80/tcp   test
287264f03a04        centos:v4           "/bin/bash"         32 minutes ago      Up 32 minutes                              centos_env

# PS

dockerd --iptables & # 只有这么启动守护进程,重启机器才能创建对应链,不带的话,重启机器还是没有DOCKER链。

所以要实现使用docker,需要修改phpstudy服务启动脚本

/usr/local/phpstudy/system/phpstudyctl
找到dockerd改为dockerd --iptables

# 关于容器里的 WorkerMan 怎么启动的(web面板服务)
通过docker inspect centos_env 也没找到CMD或者ENTRYPOINT设置启动服务。就很无奈不知道怎么启动的
在物理机的rc.local中发现启动脚本

[root@VM_0_6_centos ~]# cat /etc/rc.local 
部分略·
/usr/local/phpstudy/system/phpstudyctl -start

然后去查 phpstudyctl 这个脚本发现 start 参数对应执行的函数为 Start_Phpstudy
在此函数里才找到启动WorkerMan的方法

#start webpanel
nCount=20
pid=`/usr/local/phpstudy/system/module/getPidByExe /usr/local/phpstudy/web/php-7.3.8/bin/php`
while [[ $pid == "0" ]] && [[ $nCount>0 ]]
do
	docker exec centos_env $SHELL /usr/local/phpstudy/web/start > /dev/null 2>&1 &
	((nCount-=1))
	sleep 1
	pid=`/usr/local/phpstudy/system/module/getPidByExe /usr/local/phpstudy/web/php-7.3.8/bin/php`
done

才发现是通过执行docker exec 执行容器内命令实现的启动。

# 关于服务器防火墙配置
面板是运行在docker中,那么是怎么实现配置物理机的防火墙规则?
在 /usr/local/phpstudy/web/service/app/ 下找到 security.php 文件,部分代码如下:

<?php // 防火墙管理 require_once __DIR__.'/../app/base.php'; $user = Auth::doauth(); $type = get('type'); $req_data = json_encode(array('command'=>$type,'data'=>'','uid'=>$user['uid']));

// 防火墙状态
if($type == 'security_firwall_open' || $type== 'security_firwall_close'){
        $res = Socket::request($req_data);
        $res = json_decode($res,true);
        if($res['result']==0){
                xpexit(json_encode(array('code'=>1,'msg'=>$res['msg'])));
        }
        xpexit(json_encode(array('code'=>0,'msg'=>'操作成功')));
}

发现是调用了 Socket 类的 request 方法
最后找到是在 /usr/local/phpstudy/web/service/app/lib/socket.php 中通过socket实现。

<?php
/**
 * 网络通信模块
 */
class Socket{

        public static function request($data){
                error_reporting(E_ALL);
                set_time_limit(0);
                $host = "127.0.0.1";
                $port = 8090;

发现是连接的127.0.0.1的8090端口。也就是宿主机本身。

查看 /usr/local/phpstudy/system/phpstudyctl 脚本中的 Start_Phpstudy 函数的部分代码

#start phpstudy
pid=`/usr/local/phpstudy/system/module/getPidByExe /usr/local/phpstudy/system/phpstudy`
if [[ $pid == "0" ]];then
		/usr/local/phpstudy/system/phpstudy -d > /dev/null 2>&1 &
fi

phpstudy 这个服务就监听在8090端口。可以推测是这个服务去配置了防火墙规则。

因为容器启动时用的是host网络模式,所以可以用127.0.0.1直接访问。
这也就解释了面板虽然在容器中,但是可以操纵宿主机防火墙规则的原因。

# 关于容器的几个服务及命令
containerd
dockerd
containerd-shim
docker-proxy # 只要映射了端口,就会启动proxy
ctr
runc

root@rootop:~# /usr/bin/dockerd -h | grep config
      --config-file string                      Daemon configuration file (default "/etc/docker/daemon.json")

2019-12-26
发表者 Venus
nginx内置变量已关闭评论

nginx内置变量

$arg_PARAMETER #这个变量包含GET请求中,如果有变量PARAMETER时的值。

$args #这个变量等于请求行中(GET请求)的参数,例如foo=123&bar=blahblah;

$binary_remote_addr #二进制的客户地址。

$body_bytes_sent #响应时送出的body字节数数量。即使连接中断,这个数据也是精确的。

$content_length #请求头中的Content-length字段。

$content_type #请求头中的Content-Type字段。

$cookie_COOKIE #cookie COOKIE变量的值

$document_root #当前请求在root指令中指定的值。

$document_uri #与$uri相同。

$host #请求主机头字段,否则为服务器名称。

$hostname #Set to the machine’s hostname as returned by gethostname

$http_HEADER

$is_args #如果有$args参数,这个变量等于”?”,否则等于””,空值。

$http_user_agent #客户端agent信息

$http_cookie #客户端cookie信息

$limit_rate #这个变量可以限制连接速率。

$query_string #与$args相同。

$request_body_file #客户端请求主体信息的临时文件名。

$request_method #客户端请求的动作,通常为GET或POST。

$remote_addr #客户端的IP地址。

$remote_port #客户端的端口。

$remote_user #已经经过Auth Basic Module验证的用户名。

$request_completion #如果请求结束,设置为OK. 当请求未结束或如果该请求不是请求链串的最后一个时,为空(Empty)。

$request_method #GET或POST

$request_filename #当前请求的文件路径,由root或alias指令与URI请求生成。

$request_uri #包含请求参数的原始URI,不包含主机名,如:”/foo/bar.php?arg=baz”。不能修改。

$scheme #HTTP方法(如http,https)。

$server_protocol #请求使用的协议,通常是HTTP/1.0或HTTP/1.1。

$server_addr #服务器地址,在完成一次系统调用后可以确定这个值。

$server_name #服务器名称。

$server_port #请求到达服务器的端口号。

$uri #不带请求参数的当前URI,$uri不包含主机名,如”/foo/bar.html”。

2019-12-26
发表者 Venus
nginx下载防盗链模块之ngx_http_secure_link_module安全下载已关闭评论

nginx下载防盗链模块之ngx_http_secure_link_module安全下载

nginx模块之ngx_http_secure_link_module安全下载

此模块可以对下载链接,比如/download/nginx.tar.gz 进行身份认证,一般用于只允许站内用户下载,节省带宽。
当在页面中用户点击”下载”按钮或者点击下载链接时,链接中带着身份认证及失效时间参数。
请求进入nginx后,nginx会使用secure_link模块对认证信息及失效时间进行校验后进行下载操作或者拒绝。

参考官方文档:http://nginx.org/en/docs/http/ngx_http_secure_link_module.html

server
{
	listen 80;
	server_name localhost server_default;
	root /usr/local/nginx/html;

	location /download/
	{
		secure_link $arg_md5,$arg_expires;
		secure_link_md5 "111111$uri$secure_link_expires";

		if ($secure_link = "")
		{
			return 401; # 这里返回401状态码,跟basic auth 一样
		}

		if ($secure_link = "0")
		{
			return 410; # 返回资源被删除状态码
		}
	}
}

nginx配置参数解释:

secure_link 从请求uri中获取指定的参数,下面两个即为获取的参数。
$arg_md5 #代表从nginx内置变量 $arg_PARAMETER (GET方法中) 获取参数值,比如 /index.php?md5=xxx,则$arg_md5等于xxx
$arg_expires # 同上

secure_link_md5 根据自定义密码+请求的uri+过期时间的时间戳 算出md5值
将此值与请求中的md5参数对比是否一致,如果不一致,则 $secure_link 变量为空字符串
如果两个值一致,则检查过期时间,如果过期了,则 $secure_link 变量为0,否则为1。

php参考生成下载链接:

<?php
// 自定义密码,跟nginx里一致
$secret = "111111";
// 文件uri
$uri = "/download/nginx.tar.gz";
// 失效时间,这里为300秒,可以设置一天。
$expire = time() + 300;
$md5 = md5($secret.$uri.$expire,true);
$md5 = base64_encode($md5);
// 把+替换为-,把/替换为_
$md5 = strtr($md5, '+/', '-_');
// 把=替换为空
$md5 = str_replace('=', '', $md5);
$url = "http://www.rootop.org:10000/download/nginx.tar.gz?md5={$md5}&expires={$expire}";
echo $url;

然后用此$url去访问。

shell生成下载链接:

root@rootop:~# echo -n '111111/download/nginx.tar.gz1577360554' | openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =

ps:这里时间戳不是实时获取,也没加超时时间。