运维知识体系

运维知识体系

运维知识体系总结,持续更新,欢迎转载。
缓存知识体系

缓存知识体系

运维知识体系之缓存,分层多级缓存体系。
新运维课堂

新运维课堂

开启运维新征程!
我买云

我买云

企业级开源运维解决方案

使用kubeadm半自动安装kubernetes 1.13高可用集群(使用calico网络)

kubeadm手动安装kubernetes 1.13高可用集群  初始化集群:配置hosts文件 vim /etc/hosts192.168.3.147test-master01 192.168.3.148test-master02 192.168.3.149
继续阅读 »
kubeadm手动安装kubernetes 1.13高可用集群
 初始化集群:配置hosts文件
vim /etc/hosts
192.168.3.147test-master01
192.168.3.148test-master02
192.168.3.149test-master03
192.168.3.150test-work01
配置免密登录
ssh-keygen
ssh-copy-id test-master01
ssh-copy-id test-master02
ssh-copy-id test-master03
ssh-copy-id test-work01
设置参数
  • 关闭防火墙
systemctl stop firewalldsystemctl disable firewalld
  • 关闭swap
swapoff -ased -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
修改 /etc/fstab 文件,注释掉 SWAP 的自动挂载,使用free -m确认swap已经关闭。
  • 关闭selinux
sed-i 's/SELINUX=permissive/SELINUX=disabled/' /etc/sysconfig/selinuxsetenforce0
  • 配置转发相关参数,否则可能会出错
cat <<EOF > /etc/sysctl.d/k8s.confnet.bridge.bridge-nf-call-ip6tables = 1net.bridge.bridge-nf-call-iptables = 1net.ipv4.ip_forward=1net.ipv4.tcp_tw_recycle=0vm.swappiness=0vm.overcommit_memory=1vm.panic_on_oom=0fs.inotify.max_user_watches=89100fs.file-max=52706963fs.nr_open=52706963net.ipv6.conf.all.disable_ipv6=1net.netfilter.nf_conntrack_max=2310720EOFsysctl --system
以上在所有的Kubernetes节点执行命令使修改生效
  • kube-proxy开启ipvs
在所有work节点执行:
cat > /etc/sysconfig/modules/ipvs.modules <<EOF#!/bin/bashmodprobe -- ip_vsmodprobe -- ip_vs_rrmodprobe -- ip_vs_wrrmodprobe -- ip_vs_shmodprobe -- nf_conntrack_ipv4EOFchmod 755 /etc/sysconfig/modules/ipvs.modules && bash /etc/sysconfig/modules/ipvs.modules && lsmod | grep -e ip_vs -e nf_conntrack_ipv4
上面脚本创建了的/etc/sysconfig/modules/ipvs.modules文件,保证在节点重启后能自动加载所需模块。 使用lsmod | grep -e ip_vs -e nf_conntrack_ipv4命令查看是否已经正确加载所需的内核模块.接下来还需要确保各个节点上已经安装了ipset软件包yum install ipset。 为了便于查看ipvs的代理规则,最好安装一下管理工具ipvsadm yum install ipvsadm
yum install ipset -yyum install ipvsadm -y
如果以上前提条件如果不满足,则即使kube-proxy的配置开启了ipvs模式,也会退回到iptables模式
  • 系统优化参数

systemctl enable ntpdate.service
echo '*/30 * * * * /usr/sbin/ntpdate time7.aliyun.com >/dev/null 2>&1'> /tmp/crontab2.tmp
crontab /tmp/crontab2.tmp
systemctl start ntpdate.service
echo "* soft nofile 65536" >> /etc/security/limits.conf
echo "* hard nofile 65536" >> /etc/security/limits.conf
echo "* soft nproc 65536" >>/etc/security/limits.conf
echo "* hard nproc 65536" >>/etc/security/limits.conf
echo "* soft memlock unlimited" >> /etc/security/limits.conf
echo "* hard memlock unlimited" >>/etc/security/limits.conf

 安装docker
yum install -y epel-release
yum install -y yum-utils device-mapper-persistent-data lvm2 net-tools conntrack-toolswget vim ntpdate libseccomp libtool-ltdltelnet rsync bind-utils
yum install -y https://mirrors.aliyun.com/docker-ce/linux/centos/7/x86_64/stable/Packages/docker-ce-18.06.1.ce-3.el7.x86_64.rpm
配置docker国内镜像:
所有节点安装docker
编辑/etc/docker/daemon.json,添加以下一行
{
"registry-mirrors":["https://registry.docker-cn.com"]
}

重启docker
systemctl daemon-reload
systemctl enable docker
systemctl start docker

注:如果使用overlay2的写法:
daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "10"
},
"registry-mirrors": ["https://pqbap4ya.mirror.aliyuncs.com"],
"storage-driver": "overlay2",
"storage-opts":["overlay2.override_kernel_check=true"]
}

如果要使用overlay2,前提条件为使用ext4,如果使用xfs,需要格式化磁盘加上参数 mkfs.xfs -n ftype=1 /path/to/your/device ,ftype=1这个参数需要配置为1
 安装keepalived+haproxy
三台master 节点

VIP : 192.168.3.80
 安装 kubeadm, kubelet 和 kubectl
所有节点都执行设置yum源
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
安装组件
yum install -y kubelet-1.13.1 kubeadm-1.13.1 kubectl-1.13.1
开机启动
systemctl enable kubelet.service

 初始化K8S集群编辑kubeadm配置文件:
下面配置是kubeadm安装etcd写法:
cat > kubeadm-config.yaml << EOF
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
kubernetesVersion: v1.13.1
apiServer:
certSANs:
- "192.168.3.80"
controlPlaneEndpoint: "192.168.3.80:8443"
networking:
podSubnet: "10.50.0.0/16"
imageRepository: "harbor.oneitfarm.com/k8s-cluster-images"
EOF

CNI使用Calico,设置podSubnet: “10.50.0.0/16”
192.168.3.80是刚才安装haproxy+keepalived的VIP初始化第一个master
kubeadm init --config kubeadm-config.yaml
...
[root@master01 ~]# mkdir -p $HOME/.kube
[root@master01 ~]# cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
[root@master01 ~]# chown $(id -u):$(id -g) $HOME/.kube/config
安装网络插件
按官网方式:
Installing with the Kubernetes API datastore—50 nodes or less:
kubectl apply -f \
https://docs.projectcalico.org/v3.3/getting-started/kubernetes/installation/hosted/rbac-kdd.yaml

kubectl apply -f \
https://docs.projectcalico.org/v3.3/getting-started/kubernetes/installation/hosted/kubernetes-datastore/calico-networking/1.7/calico.yaml

以上建议先wget下来,需要根据自己网络修改配置 :
- name: CALICO_IPV4POOL_CIDR
value: "10.50.0.0/16"
复制相关文件到其他master节点
ssh root@master02 mkdir -p /etc/kubernetes/pki/etcd
scp /etc/kubernetes/admin.conf root@master02:/etc/kubernetes
scp /etc/kubernetes/pki/{ca.*,sa.*,front-proxy-ca.*} root@master02:/etc/kubernetes/pki
scp /etc/kubernetes/pki/etcd/ca.* root@master02:/etc/kubernetes/pki/etcd
部署master-other
在其它slave节点上执行下面命令,加入集群
kubeadm join 192.168.3.80:8443 --token pv2a9n.uh2yx1082ffpdf7n --discovery-token-ca-cert-hash sha256:872cac35b0bfec28fab8f626a727afa6529e2a63e3b7b75a3397e6412c06ebc5 --experimental-control-plane
kube-proxy开启ipvs
修改ConfigMap的kube-system/kube-proxy中的config.conf,mode: “ipvs”:
kubectl edit configmap kube-proxy -n kube-system
kubectl get pod -n kube-system | grep kube-proxy | awk '{system("kubectl delete pod "$1" -- grace-period=0 --force -n kube-system")}'

 检查测试查看kubernetes集群状态
kubectl get nodes -o wide
kubectl get cs
NAME STATUS MESSAGE ERROR
controller-manager Healthy ok
scheduler Healthy ok
etcd-0 Healthy {"health": "true"}
查看etcd集群状态
本文通过kubeadm自动安装etcd,也就是docker方式安装的etcd,可以exec进去容器内检查:
kubectl exec -ti -n kube-system etcd-an-master01 sh
/ # export ETCDCTL_API=3
/ # etcdctl --endpoints=https://[127.0.0.1]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key member list
安装失败清理集群
集群初始化如果遇到问题,可以使用下面的命令进行清理:
kubeadm reset
systemctl stop kubelet
systemctl stop docker
rm -rf /var/lib/cni/
rm -rf /var/lib/kubelet/*
rm -rf /etc/cni/
ifconfig cni0 down
ifconfig flannel.1 down
ifconfig docker0 down
ip link delete cni0
ip link delete flannel.1
systemctl start docker

 设置资源调度
使用kubeadm初始化的集群,出于安全考虑Pod不会被调度到Master Node上,也就是说Master Node不参与工作负载。这是因为当前的master节点被打上了node-role.kubernetes.io/master:NoSchedule的污点:
kubectl describe node master01 | grep Taint
Taints: node-role.kubernetes.io/master:NoSchedule

检查join进集群的master和work节点,如果调度不对,可以通过如下方式设置:
[root@an-master01 ~]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
an-master01 Ready master 4h39m v1.13.1
an-master02 Ready <none> 4h32m v1.13.1
an-master03 Ready master 86m v1.13.1
an-work01 Ready <none> 85m v1.13.1

查看当前状态:
kubectl describe nodes/an-master02 |grep -E '(Roles|Taints)'
Roles: <none>
Taints: <none>

设置为master节点且不调度:
kubectl label node an-master02 node-role.kubernetes.io/master=
kubectl taint nodes an-master02 node-role.kubernetes.io/master=:NoSchedule
如果想去除限制的话:
kubectl taint nodes an-master03 node-role.kubernetes.io/master-

work节点设置:
kubectl label node an-work01 node-role.kubernetes.io/work=
kubectl describe nodes/an-work01 |grep -E '(Roles|Taints)'
Roles: work
Taints: <none>

  收起阅读 »

  使用kubeadm部署Kubernetes v1.13.3

    学习Kubernetes没有什么特殊技巧,需要循序渐进,理论结合实践,不管目前生产中是否使用K8S,作为运维工程师,每个人都应该有自己的一个K8S集群,才能开启探索之路。拒绝夸夸其谈,前后不一致!!!       请查看下面的链接:       htt
继续阅读 »
    学习Kubernetes没有什么特殊技巧,需要循序渐进,理论结合实践,不管目前生产中是否使用K8S,作为运维工程师,每个人都应该有自己的一个K8S集群,才能开启探索之路。拒绝夸夸其谈,前后不一致!!!
 
    请查看下面的链接:
 
    https://mp.weixin.qq.com/s?__biz=MzI5MjA5Mjg5OA==&mid=2247484395&idx=1&sn=0767cc24ec99ce818e41f7c40dda5d23&chksm=ec07ed66db7064707b05a9dda9c23882b9106577fc5f1ef45a9d78e6a93a57a5f507210207ae&token=1791180619&lang=zh_CN#rd 收起阅读 »

【Kubernetes实践指南】5.1-Kubernetes架构介绍

【前言】       2019年是互联网寒冬几乎成为不争的事实,在寒冬下,往往会暴露出很多被掩盖的事实,就像下面这个案例,看完之后,深有感触。   作为运维工程师,坚持学习才是寒冬中的生存之道。Kubernetes的浪潮已经来了,不管我们是否
继续阅读 »
【前言】
 
    2019年是互联网寒冬几乎成为不争的事实,在寒冬下,往往会暴露出很多被掩盖的事实,就像下面这个案例,看完之后,深有感触。

微信图片_20190211183731.jpg

 
作为运维工程师,坚持学习才是寒冬中的生存之道。Kubernetes的浪潮已经来了,不管我们是否愿意,生产中是否使用,Kubernetes已经不知不觉成为运维必备技能。本文介绍Kubernetes架构,作为入门的第一篇文章。    
 
Kubernetes介绍
    Kubernetes源于希腊语,意为“舵手”或“飞行员”,是用于自动部署,扩展和管理容器化应用程序的开源系统,由于K和S之间有8个字母,被简称为K8S。Kubernetes构建在Google 15年生产环境经验基础之上,可以将Kubernetes看作为Google内部的容器管理平台Borg和Omega的开源版本,当然他们之间是有一些差异的。
 
Kubernetes系统架构
    Kubernetes被设计为Master和Node两个角色,Master为控制节点,Node为计算节点或者叫工作节点,在Master节点上有一个API Server服务,对外提供标准的RestAPI,这也是Kubernetes集群的入口,意外着只要和集群进行交互必须连接到API Server上。

1.png

 
Master节点介绍
Kubernetes Master节点主要有4个组件,API Server、Scheduler、Controller、etcd。如下图所示:

2.png

  • API Server:提供Kubernetes API接口,主要处理Rest操作以及更新Etcd中的对象。是所有资源增删改查的唯一入口。
  • Scheduler:绑定Pod到Node上,主要做资源调度。
  • Controller Manager:所有其他群集级别的功能,目前由控制器Manager执行。资源对象的自动化控制中心,Kubernetes集群有很多控制器。
  • Etcd:所有持久化的状态信息存储在Etcd中,这个是Kubernetes集群的数据库。
 Node节点介绍Node节点是Kubernetes集群的工作节点,在Node节点上主要运行了Docker、Kubelet、kube-proxy三个服务(Fluentd请先忽略),如下图所示:
3.png
  • Docker Engine:负责节点的容器的管理工作,最终创建出来的是一个Docker容器。
  • Kubelet:安装在Node上的代理服务,和API Server进行通信,用来管理Pods以及容器、镜像、Volume等,实现对集群对节点的管理。
  • Kube-proxy:安装在Node上的网络代理服务,提供网络代理以及负载均衡,实现与Service通讯。
 Kubernetes逻辑架构在上面的介绍中提到像Pod、Service这些概念,在Kubernetes的系统架构图中并没有体现出来,这些可以理解为Kubernetes逻辑架构中的组件。也就是在Master和Node上并不具体存在的一个服务或者进程,但却是Kubernetes的组件,也是我们的管理对象。主要有Pod、Service和各种控制器等。 PodPod是Kubernetes的最小管理单元,一个Pod可以包含一组容器和卷。虽然一个Pod里面可以包含一个或者多个容器,但是Pod只有一个IP地址,而且Pod重启后,IP地址会发生变化。
pod.png
 Controller一个应用如果可以有一个或者多个Pod,就像你给某一个应用做集群,集群中的所有Pod是一模一样的。Kubernetes中有很多控制器可以来管理Pod,例如下图的Replication Controller可以控制Pod的副本数量,从而实现横向扩展。
rc.png
 Kubernetes中有很多控制器,后面的章节我们会逐步讲到,常用的控制器如下:
  • Replication Controller(新版本已经被ReplicaSet所替代)
  • ReplicaSet(新版本被封装在Deployment中)
  • Deployment:封装了Pod的副本管理、部署更新、回滚、扩容、缩容等功能。
  • DaemonSet:保证所有的Node上有且只有一个Pod在运行。
  • StatefulSet:有状态的应用,为Pod提供唯一的标识,它可以保证部署和scale的顺序。
  • Job:使用Kubernetes运行单一任务。
  • CronJob:使用Kubernetes运行定时任务。

Service
由于Pod的生命周期是短暂的,而且每次重启Pod的IP地址都会发生变化,而且一个Pod有多个副本,也就是说一个集群中有了多个节点,就需要考虑负载均衡的问题。Kubernetes使用Service来实现Pod的访问,而且Service有一个Cluster IP,通常也称之为VIP,是固定不变的。

service.png

 
Kubernetes网络介绍
在Kubernetes集群中存在着三种网络,分别是Node网络、Pod网络和Service网络,这几种网络之间的通信需要依靠网络插件,Kubernetes本身并没有提供,社区提供了像Flannel、Calico、Canal等,后面章节会详述。
Node网络
Node网络指的是Kubernetes Node节点本地的网络,在本实验环境中使用的是192.168.56.0/24这个网段,所有的Node和Master在该网段都可以正常通信。
Pod网络
后面创建的Pod,每一个Pod都会有一个IP地址,这个IP地址网络段被称之为Pod网络,如下图所示。
[root@linux-node1 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-54458cd494-hpn68 1/1 Running 0 9m7s 10.2.1.2 linux-node2.linuxhot.com <none> <none>
nginx-54458cd494-r4mfq 1/1 Running 0 7m46s 10.2.1.3 linux-node2.linuxhot.com <none> <none>



 
Service网络
Service是为Pod提供访问和负载均衡的网络地址段,如下图所示。
 [root@linux-node1 ~]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.1.0.1 <none> 443/TCP 64m
nginx NodePort 10.1.216.23 <none> 80:30893/TCP 8m3s





Kubernetes的组件和知识绝非如此,快速入门可以先了解这么多,下一章节,我们先快速的部署一个Kubernetes集群。
 
来新运维社区分享知识!
 
    欢迎来新运维社区分享你的原创文章、博文、理念或者是对运维工程师有帮助的任何知识,如要加入K8S中国交流群,请添加新运维-小助手,备注:姓名-K8S。
(无备注不予处理,请理解!)

小助手微信.jpg

【小助手微信】
 
END -
 
 
加入新运维社区,开启新征程!       
 
牛人并不可怕,可怕的是牛人比我们还努力!
 
QQ截图20190211183631.png
收起阅读 »

浅谈字典跟列表优缺点

字典跟列表优缺点: 非关系型数据库的redis跟memcached是以key-value健值对的数据类型,那么站在这个角度来讲就可以理解为是字典,而关系型数据库mysql二维表可以理解为是列表。 和list比较,dict有以下几个特点(Redis): 1
继续阅读 »
字典跟列表优缺点:
非关系型数据库的redis跟memcached是以key-value健值对的数据类型,那么站在这个角度来讲就可以理解为是字典,而关系型数据库mysql二维表可以理解为是列表。
和list比较,dict有以下几个特点(Redis):
1、查找和插入的速度极快,不会随着key的增加而变慢; 
2、需要占用大量的内存,内存浪费多。
而list相反(Mysql):
1、查找和插入的时间随着元素的增加而增加; 
2、占用空间小,浪费内存很少。
所以,dict(Redis)是用空间来换取时间的一种方法 收起阅读 »

iTop自定义CI模型

iTop自定义CI模型,需要Toolkit工具,附件中。   1.下载附件中的Toolkit。直接解压到对应的webroot下。   2.直接访问http://xxx/toolkit访问。    
iTop自定义CI模型,需要Toolkit工具,附件中。
 
1.下载附件中的Toolkit。直接解压到对应的webroot下。
 
2.直接访问http://xxx/toolkit访问。
 
 

LNMP(Linux+Nginx+PHP+MySQL)生产源码部署实战

       日常工作中,部署Nginx+PHP是非常普通的一个场景,如果有一个最佳实践,每次可以直接复制粘贴就好了。本文正是完全生产实践的方式。Nginx和PHP均推荐使用源码安装,MySQL推荐直接使用官方提供编译好的二进制文件,性能是最佳的,除非你可以非
继续阅读 »
 
     日常工作中,部署Nginx+PHP是非常普通的一个场景,如果有一个最佳实践,每次可以直接复制粘贴就好了。本文正是完全生产实践的方式。Nginx和PHP均推荐使用源码安装,MySQL推荐直接使用官方提供编译好的二进制文件,性能是最佳的,除非你可以非常专业的进行MySQL编译的定制。
 
1.源码安装Nginx
依赖软件包安装。
[root@linux-node1 ~]# rpm -ivh http://mirrors.aliyun.com/epel/epel-release-latest-7.noarch.rpm 
[root@linux-node1 ~]# yum install -y gcc glibc gcc-c++ pcre-devel openssl-devel
创建www用户,用于启动Nginx
[root@linux-node1 src]# useradd -s /sbin/nologin -M www
Nginx源码编译安装
[root@linux-node1 ~]# cd /usr/local/src
[root@linux-node1 src]# wget http://nginx.org/download/nginx-1.14.2.tar.gz
[root@linux-node1 src]# tar zxf nginx-1.14.2.tar.gz
[root@linux-node1 src]# cd nginx-1.14.2
[root@linux-node1 nginx-1.14.2]#./configure --prefix=/usr/local/nginx-1.14.2 \
--user=www --group=www \
--with-http_ssl_module --with-stream \
--with-http_stub_status_module --with-file-aio
[root@linux-node1 nginx-1.14.2]# make && make install
2.测试配置并启动Nginx
[root@linux-node1 ~]# ln -s /usr/local/nginx-1.14.2/ /usr/local/nginx
[root@linux-node1 ~]# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx-1.14.2/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx-1.14.2/conf/nginx.conf test is successful
[root@linux-node1 ~]# /usr/local/nginx/sbin/nginx
查看Nginx启动状态
[root@linux-node1 ~]# netstat –ntlp | grep 80
 
3.源码安装PHP(FPM)
首先,我们需要将PHP依赖的软件包都安装上。
[root@linux-node1 ~]# yum install -y gcc gcc-c++ glibc make autoconf \
libjpeg-turbo-devel libpng-devel mhash-devel \
freetype-devel libxml2-devel zlib-devel \
libcurl-devel openssl-devel swig libmcrypt-devel

4.下载软件包
[root@linux-node1 ~]# cd /usr/local/src
[root@linux-node1 src]# wget http://120.52.51.16/cn2.php.net/distributions/php-5.6.39.tar.gz
[root@linux-node1 src]# tar zxf php-5.6.39.tar.gz
[root@linux-node1 src]# cd php-5.6.39

5.编译软件包
[root@linux-node1 php-5.6.39]#./configure --prefix=/usr/local/php-5.6.39 --with-mysql \
--with-jpeg-dir --with-png-dir --with-curl --with-gd \
--with-zlib --enable-xml --with-libxml-dir --enable-bcmath \
--enable-sysvsem --enable-mbregex --with-openssl \
--enable-mbstring --enable-gd-native-ttf --enable-sockets \
--enable-shmop --enable-opcache --disable-debug \
--with-xmlrpc --enable-zip --enable-soap --enable-zip --enable-fpm \
--with-fpm-user=www --with-fpm-group=www --with-freetype-dir \
--with-mcrypt --with-mhash --enable-inline-optimization
[root@linux-node1 php-5.6.39]# make && make install

6.设置php配置文件和启动脚本
[root@linux-node1 php-5.6.39]# ln -s /usr/local/php-5.6.39/ /usr/local/php
[root@linux-node1 php-5.6.39]# cp php.ini-production /usr/local/php/lib/php.ini
[root@linux-node1 php-5.6.39]# cp sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm
[root@linux-node1 php-5.6.39]# chmod +x /etc/init.d/php-fpm

fpm配置文件
[root@linux-node1 php-5.6.39]# cd /usr/local/php/etc/	
[root@linux-node1 etc]# cp php-fpm.conf.default php-fpm.conf
[root@linux-node1 etc]# /etc/init.d/php-fpm start
Starting php-fpm done
默认情况下php-fpm会监听在本地127.0.0.1的9000端口,你也可以调整php-fpm.conf设置为监听sock文件。
 
7.配置Nginx和PHP的连接
[root@linux-node1 ~]# cd /usr/local/nginx/conf/
[root@linux-node1 conf]# vim nginx.conf
#去掉以下内容注释并修改
location ~ \.php$ {
#root html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
#fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
include fastcgi.conf; #注意修改
}
重载Nginx配置
[root@linux-node1 conf]# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx-1.14.2/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx-1.14.2/conf/nginx.conf test is successful
[root@linux-node1 conf]# /usr/local/nginx/sbin/nginx -s reload

测试PHP安装
[root@linux-node1 conf]# cd /usr/local/nginx/html/
[root@linux-node1 html]# vim info.php
<?php
phpinfo();
?>

 

QQ截图20190104152228.png

 MySQL 生产部署
    mysql的安装除了yum,还可以源码编译,其实如果你没有完全掌握源码编译的优化选项,选择官方编译好的二进制版本也是不错的选择。
下载mysql二进制包
[root@linux-node1 ~]# cd /usr/local/src
# wget https://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.37-linux-glibc2.12-x86_64.tar.gz
 
 
 
创建mysql用户
[root@linux-node1 ~]# groupadd mysql
[root@linux-node1 ~]# useradd -r -g mysql -s /bin/false mysql

解压mysql二进制包
[root@linux-node1 ~]# cd /usr/local/src
[root@linux-node1 src]# tar zxf mysql-5.6.37-linux-glibc2.12-x86_64.tar.gz
[root@linux-node1 src]# mv mysql-5.6.30-linux-glibc2.5-x86_64 /usr/local/
[root@linux-node1 ~]# chown -R mysql:mysql /usr/local/mysql-5.6.30-linux-glibc2.5-x86_64



初始化mysql
[root@linux-node1 ~]# ln -s /usr/local/mysql-5.6.30-linux-glibc2.5-x86_64/ /usr/local/mysql
[root@linux-node1 ~]# chown -R mysql:mysql /usr/local/mysql
[root@linux-node1 ~]# /usr/local/mysql/scripts/mysql_install_db \
--defaults-file=/usr/local/mysql/my.cnf \
--user=mysql --basedir=/usr/local/mysql/ \
--datadir=/usr/local/mysql/data

启动mysql
[root@linux-node1 src]# /usr/local/mysql/bin/mysqld_safe --defaults-file=/usr/local/mysql/my.cnf &



 
PHP安装PDO MySQL扩展
    要让PHP连接到MySQL。可以使用PDO扩展,安装PDO扩展和安装其它PHP模块的方法都是类似的,分为三个步骤:
第一步:安装PDO扩展:
[root@linux-node1 ~]# cd /usr/local/src/php-5.6.21/ext/pdo_mysql/
phpize是用来扩展php扩展模块的,通过phpize可以建立php的外挂模块
[root@linux-node1 pdo_mysql]# /usr/local/php/bin/phpize
Configuring for:
PHP Api Version: 20131106
Zend Module Api No: 20131226
Zend Extension Api No: 220131226
[root@linux-node1 pdo_mysql]# ./configure --with-php-config=/usr/local/php/bin/php-config
[root@linux-node1 pdo_mysql]# make && make install
执行完毕,最后会输出讲扩展安装到的位置,
# ls /usr/local/php-5.6.21/lib/php/extensions/no-debug-non-zts-20131226/
opcache.a opcache.so pdo_mysql.so

第二步:修改配置文件
[root@linux-node1 ~]# vim /usr/local/php/lib/php.ini
#在文件末尾添加
extension=pdo_mysql.so
第三步:重新加载PHP(FPM)
[root@linux-node1 ~]# /etc/init.d/php-fpm reload
Reload service php-fpm done
 
 使用mysqli连接数据库,所以需要给PHP安装该模块。
[root@linux-node1 pdo_mysql]# cd /usr/local/src/php-5.6.39/ext/mysqli/
[root@linux-node1 mysqli]# /usr/local/php/bin/phpize
[root@linux-node1 mysqli]# ./configure --with-php-config=/usr/local/php/bin/php-config
[root@linux-node1 mysqli]# make && make install

[root@linux-node1 mysqli]# vim /usr/local/php/lib/php.ini
#在最下面新添加一行
extension=mysqli.so
[root@linux-node1 mysqli]# /etc/init.d/php-fpm reload

 
 安装其他PHP模块均使用类似的方法。例如安装Redis、Memcached模块等。
  收起阅读 »

Redis4.0集群

节点信息规划: 准备部署7001-7004 4个实例,但是redis集群提示最少需要3个主节点,6个节点才能完成启动,所以后面2个是后来加的。 注意:这里因为是测试环境,所以将所有实例都放在了一台机器上,生产建议主备节点不要放在一台机器上 1、
继续阅读 »
节点信息规划:
准备部署7001-7004 4个实例,但是redis集群提示最少需要3个主节点,6个节点才能完成启动,所以后面2个是后来加的。
注意:这里因为是测试环境,所以将所有实例都放在了一台机器上,生产建议主备节点不要放在一台机器上
9.png

1、环境安装
~]# cd /opt/
opt]# wget http://download.redis.io/releases/redis-4.0.11.tar.gz
opt]# tar zxf redis-4.0.11.tar.gz
opt]# cd redis-4.0.11
opt]# make test
#yum 安装tcl
opt]# yum install tcl -y
opt]# make test
#将redis的执行脚本拷贝到PATH变量所在目录或加入全局环境变量
opt]# cp -a src/redis-server src/redis-cli src/redis-sentinel src/redis-trib.rb src/redis-check-aof src/redis-check-rdb src/redis-benchmark /usr/local/bin/
2、配置redis集群文件
opt]# mkdir -p redis-cluster
opt]# mkdir -p /opt/redis-cluster/nodes-{7001,7002,7003,7004}
opt]# ln -s redis-4.0.11 redis
按需修改配置文件(以nodes-7001实例为例)
[root@shenghui-ansible opt]# egrep -v "^#|^$" redis-cluster/nodes-7001/redis.conf 
bind 192.168.56.67
protected-mode yes
port 7001
tcp-backlog 511
timeout 0
tcp-keepalive 300
daemonize yes
supervised no
pidfile redis_7001.pid
loglevel notice
logfile "/opt/redis-cluster/nodes-7001/redis_7001.log"
databases 16
always-show-logo yes
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /opt/redis-cluster/nodes-7001/
slave-serve-stale-data yes
slave-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
slave-priority 100
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
slave-lazy-flush no
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble no
lua-time-limit 5000
cluster-enabled yes
cluster-config-file nodes-7001.conf
cluster-node-timeout 5000
cluster-slave-validity-factor 10
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes
3、启动redis及创建集群
依次启动各个redis实例
]# redis-server /opt/redis-cluster/nodes-7001/redis.conf
]# redis-server /opt/redis-cluster/nodes-7002/redis.conf
]# redis-server /opt/redis-cluster/nodes-7003/redis.conf
]# redis-server /opt/redis-cluster/nodes-7004/redis.conf
4、创建redis集群
创建redis集群使用redis-trib.rb命令,该命令是基于ruby的脚本,需要安装ruby,ruby-gem以及gem-redis
#如果有老版本,卸载ruby2.0.0,安装ruby2.2.2以上版本
[root@elk01 ~]# ruby -v  #centos7 忽略
ruby 2.0.0p648 (2015-12-16) [x86_64-linux]
[root@elk01 ~]# yum erase ruby
~]# wget https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.3.tar.gz
~]# tar zxf ruby-2.5.3.tar.gz
~]# cd ruby-2.5.3/
ruby-2.5.3]# ./configure
ruby-2.5.3]# make
安装ruby-redis.gem
推荐:如果默认国外的gem源连不上或者很慢,可以试试国内源
添加国内源命令:gem source -a https://gems.ruby-china.com
删除国外源并添加国内源:gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.com/
#检测gem源,注意RubyGems源尽量用新版本,官方建议2.6.X,但是我用2.5.2也没什么问题
[root@shenghui-ansible ~]# gem source -l
*** CURRENT SOURCES ***

https://gems.ruby-china.com
redis-cluster]# gem install redis -v 3.3.5 #注:不要使用4.x版本,不然后期增加实例,迁移哈希槽会失败
创建集群报错:
root@elk01 redis-cluster]# redis-trib.rb create --replicas 1 192.168.56.67:7001 192.168.56.67:7002 192.168.56.67:7003 192.168.56.67:7004
>>> Creating cluster
*** ERROR: Invalid configuration for cluster creation. #错误表示redis集群至少要3个主,6个节点
*** Redis Cluster requires at least 3 master nodes.
*** This is not possible with 4 nodes and 1 replicas per node.
*** At least 6 nodes are required.
#新增两个节点
redis-cluster]# mkdir -p nodes-{7005,7006}
#拷贝并修改下redis配置ip
redis-cluster]# cp nodes-7001/redis.conf nodes-7005/
redis-cluster]# cp nodes-7001/redis.conf nodes-7006/
启动redis
redis-server /opt/redis-cluster/nodes-7005/redis.conf
redis-server /opt/redis-cluster/nodes-7006/redis.conf
[root@shenghui-ansible ~]# ps -ef|grep redis
root 26651 1 0 Nov13 ? 00:01:10 redis-server 127.0.0.1:7002 [cluster]
root 26656 1 0 Nov13 ? 00:01:10 redis-server 127.0.0.1:7003 [cluster]
root 26661 1 0 Nov13 ? 00:01:09 redis-server 127.0.0.1:7004 [cluster]
root 26666 1 0 Nov13 ? 00:01:10 redis-server 127.0.0.1:7005 [cluster]
root 26671 1 0 Nov13 ? 00:01:09 redis-server 127.0.0.1:7006 [cluster]
root 26725 1 0 Nov13 ? 00:01:11 redis-server 127.0.0.1:7001 [cluster]
#创建集群成功
redis-cluster]# redis-trib.rb create --replicas 1 192.168.56.67:7001 192.168.56.67:7002 192.168.56.67:7003 192.168.56.67:7004 192.168.56.67:7005 192.168.56.67:7006
>>> Creating cluster
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
192.168.56.67:7001
192.168.56.67:7002
192.168.56.67:7003
Adding replica 192.168.56.67:7004 to 192.168.56.67:7001
Adding replica 192.168.56.67:7005 to 192.168.56.67:7002
Adding replica 1192.168.56.67:7006 to 192.168.56.67:7003
M: 23df12c2bafde34f5bdd53d3463ad20b8ab507d2 192.168.56.67:7001
集群测试:
[root@shenghui-ansible ~]# redis-cli -c -p 7001 #登陆操作集群,-c参数是必须的
127.0.0.1:7001> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:7
cluster_my_epoch:7
cluster_stats_messages_ping_sent:84881
...
127.0.0.1:7001> cluster nodes
21da5db87c968a926ac98db20c6b3e5db98e86b4 192.168.56.67:7001@17001 myself,slave 64f7850f021d1150b41fc654f247e075f8573813 0 1542181496000 1 connected
bbba5557051520f7d825819415a806b8716f811c 127.0.0.1:7002@17002 master - 0 1542181497150 2 connected 5461-10922
f707863e9846cf9e7c68fd7cdae5b72aa4f6839b 127.0.0.1:7004@17004 slave 66ebc6a414a533a9a2ed27fbcdbea88b9d8ed4db 0 1542181498161 4 connected
ecdf34327e274a8294f7225ddbb5d3f4cd0b7bde 127.0.0.1:7006@17006 slave bbba5557051520f7d825819415a806b8716f811c 0 1542181497000 6 connected
64f7850f021d1150b41fc654f247e075f8573813 127.0.0.1:7005@17005 master - 0 1542181495000 7 connected 0-5460
66ebc6a414a533a9a2ed27fbcdbea88b9d8ed4db 127.0.0.1:7003@17003 master - 0 1542181495132 3 connected 10923-16383
127.0.0.1:7001> set liubai 'papa'
-> Redirected to slot [11252] located at 127.0.0.1:7003
OK
127.0.0.1:7003> get liubai
"papa"
nodes-7001宕机查看数据(redis集群去中心化,连接哪一台都可以):
~]# kill -9 26725  #生长不建议使用kill,登陆redis-cli 使用shutdown关闭redis
[root@shenghui-ansible ~]# redis-cli -c -p 7002
127.0.0.1:7002> get liubai
-> Redirected to slot [11252] located at 127.0.0.1:7003 #数据在nodes3,从自动切换选举为主
"papa"
127.0.0.1:7003> cluster nodes
bbba5557051520f7d825819415a806b8716f811c 127.0.0.1:7002@17002 master - 0 1542181782880 2 connected 5461-10922
21da5db87c968a926ac98db20c6b3e5db98e86b4 127.0.0.1:7001@17001 slave,fail 64f7850f021d1150b41fc654f247e075f8573813 1542181666613 1542181664000 7 disconnected
64f7850f021d1150b41fc654f247e075f8573813 127.0.0.1:7005@17005 master - 0 1542181780860 7 connected 0-5460
66ebc6a414a533a9a2ed27fbcdbea88b9d8ed4db 127.0.0.1:7003@17003 myself,master - 0 1542181781000 3 connected 10923-16383
f707863e9846cf9e7c68fd7cdae5b72aa4f6839b 127.0.0.1:7004@17004 slave 66ebc6a414a533a9a2ed27fbcdbea88b9d8ed4db 0 1542181781869 4 connected
ecdf34327e274a8294f7225ddbb5d3f4cd0b7bde 127.0.0.1:7006@17006 slave bbba5557051520f7d825819415a806b8716f811c 0 1542181780000 6 connected
127.0.0.1:7003> get liubai
"papa"
收起阅读 »

Zabbix API初步入门

    Zabbix API是实现Zabbix自动化监控的主要手段之一,你可以使用Zabbix Agent的自动注册功能,也可以使用网络自动发现功能,但是这些功能各有各的缺点。如果编写脚本对你来说没有压力,我跟建议Zabbix的所有自动化功能全部使用API的方
继续阅读 »
    Zabbix API是实现Zabbix自动化监控的主要手段之一,你可以使用Zabbix Agent的自动注册功能,也可以使用网络自动发现功能,但是这些功能各有各的缺点。如果编写脚本对你来说没有压力,我跟建议Zabbix的所有自动化功能全部使用API的方式来完成。
 

图片1.png

 
 
1.登录API接口
 
## 登录认证
curl -s -X POST -H 'Content-Type: application/json' -d '
{
"jsonrpc": "2.0",
"method": "user.login",
"params": {
"user": "Admin",
"password": "zabbix"
},
"id": 1
}' http://192.168.56.11/zabbix/api_jsonrpc.php | python -m json.tool

 


执行完毕,会访问一个Auth Token,需要保留下来,后面的操作中需要使用


 
2.使用Python调用Zabbix API
 
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import requests
import json

url = 'http://zabbix.unixhot.com/zabbix/api_jsonrpc.php'
post_data = {
"jsonrpc": "2.0",
"method": "user.login",
"params": {
"user": "api-user",
"password": "zabbix"
},
"id": 1
}
post_header = {'Content-Type: application/json'}

ret = requests.post(url, data=json.dumps(post_data), headers=post_header)
print ret.text

 
3.显示所有主机:注意替换auth token。
 
## 显示所有主机
curl -s -X POST -H 'Content-Type: application/json' -d '
{
"jsonrpc": "2.0",
"method": "host.get",
"params": {
"output": ["host"]
},
"auth": "3593bc5a0a3b62a5b7f8c3a9c3b9f59f",
"id": 1
}' http://192.168.56.11/zabbix/api_jsonrpc.php | python -mjson.tool

 
查询所有主机组:
## 显示所有主机组
curl -s -X POST -H 'Content-Type: application/json' -d '
{
"jsonrpc": "2.0",
"method": "hostgroup.get",
"params": {
"output": "extend"
},
"auth": "3593bc5a0a3b62a5b7f8c3a9c3b9f59f",
"id": 1
}' http://192.168.56.11/zabbix/api_jsonrpc.php | python -mjson.tool

 
查询所有模板:
## 显示所有模版
curl -s -X POST -H 'Content-Type: application/json' -d '
{
"jsonrpc": "2.0",
"method": "template.get",
"params": {
"output": "extend"
},
"auth": "3593bc5a0a3b62a5b7f8c3a9c3b9f59f",
"id": 1
}' http://192.168.56.11/zabbix/api_jsonrpc.php | python -mjson.tool
收起阅读 »

SaltStack自动化部署Zabbix Agent

     Zabbix Agent建议在系统初始化的时候直接使用SaltStack进行自动化部署,以后的管理和变更均使用SaltStack来完成。   1.准备SaltStack环境(略)   注:可以参考SaltStack专栏里面的SaltStack快速入门
继续阅读 »
     Zabbix Agent建议在系统初始化的时候直接使用SaltStack进行自动化部署,以后的管理和变更均使用SaltStack来完成。
 
1.准备SaltStack环境(略)
 
注:可以参考SaltStack专栏里面的SaltStack快速入门。
 
2.创建对应的目录
[root@linux-node1 ~]# mkdir /srv/salt/base/zabbix/files/ -p

3.准备Zabbix配置文件,手动安装zabbix-agent后,copy文件到files目录下:
[root@linux-node1 ~]# yum install -y zabbix-agent
[root@linux-node1 ~]# cp /etc/zabbix/zabbix_agentd.conf /srv/salt/base/zabbix/files/

4.修改zabbix_agentd.conf为模板文件
[root@linux-node1 ~]# vim /srv/salt/base/zabbix/files/zabbix_agentd.conf 
Server={{ ZABBIX_SERVER }}
ServerActive={{ ZABBIX_ACTIVE_SERVER }}
Hostname={{ AGENT_HOSTNAME }}

5.编写状态文件:
 
[root@linux-node1 ~]# vim /srv/salt/base/zabbix/zabbix-agent.sls
zabbix-repo:
cmd.run:
- name: rpm -i https://repo.zabbix.com/zabbix/4.0/rhel/7/x86_64/zabbix-release-4.0-1.el7.noarch.rpm
- unless: rpm -qa | grep zabbix-release

zabbix-agent:
pkg.installed:
- name: zabbix-agent
- version: 4.0.2-1.el7
- require:
- cmd: zabbix-repo
file.managed:
- name: /etc/zabbix/zabbix_agentd.conf
- source: salt://zabbix/files/zabbix_agentd.conf
- template: jinja
- defaults:
ZABBIX_SERVER: 192.168.56.11,192.168.56.12
ZABBIX_ACTIVE_SERVER: 192.168.56.11:10051,192.168.56.12:10051
AGENT_HOSTNAME: {{ grains['fqdn'] }}
- require:
- pkg: zabbix-agent
service.running:
- enable: True
- watch:
- pkg: zabbix-agent
- file: zabbix-agent

6.执行状态
 
[root@linux-node1 ~]# salt '*' state.sls zabbix.zabbix-agent
linux-node1.example.com:
----------
ID: zabbix-repo
Function: cmd.run
Name: rpm -i https://repo.zabbix.com/zabbix/4.0/rhel/7/x86_64/zabbix-release-4.0-1.el7.noarch.rpm
Result: True
Comment: unless condition is true
Started: 17:19:44.412981
Duration: 938.341 ms
Changes:
----------
ID: zabbix-agent
Function: pkg.installed
Result: True
Comment: All specified packages are already installed and are at the desired version
Started: 17:19:49.124383
Duration: 2037.406 ms
Changes:
----------
ID: zabbix-agent
Function: file.managed
Name: /etc/zabbix/zabbix_agentd.conf
Result: True
Comment: File /etc/zabbix/zabbix_agentd.conf is in the correct state
Started: 17:19:51.165575
Duration: 53.529 ms
Changes:
----------
ID: zabbix-agent
Function: service.running
Result: True
Comment: The service zabbix-agent is already running
Started: 17:19:51.220700
Duration: 56.149 ms
Changes:

Summary for linux-node1.example.com
------------
Succeeded: 4
Failed: 0
------------
Total states run: 4
Total run time: 3.085 s
linux-node2.example.com:
----------
ID: zabbix-repo
Function: cmd.run
Name: rpm -i https://repo.zabbix.com/zabbix/4.0/rhel/7/x86_64/zabbix-release-4.0-1.el7.noarch.rpm
Result: True
Comment: unless condition is true
Started: 17:19:45.512368
Duration: 1108.944 ms
Changes:
----------
ID: zabbix-agent
Function: pkg.installed
Result: True
Comment: All specified packages are already installed and are at the desired version
Started: 17:19:49.699172
Duration: 1787.491 ms
Changes:
----------
ID: zabbix-agent
Function: file.managed
Name: /etc/zabbix/zabbix_agentd.conf
Result: True
Comment: File /etc/zabbix/zabbix_agentd.conf is in the correct state
Started: 17:19:51.490851
Duration: 53.646 ms
Changes:
----------
ID: zabbix-agent
Function: service.running
Result: True
Comment: The service zabbix-agent is already running
Started: 17:19:51.546009
Duration: 63.202 ms
Changes:

Summary for linux-node2.example.com
------------
Succeeded: 4
Failed: 0
------------
Total states run: 4
Total run time: 3.013 s

 
 
  收起阅读 »

Zabbix告警脚本-Python发送邮件

自定义Zabbix告警,首先需要编写告警脚本,告警脚本需要支持3个参数:        1.告警接收人      2.主题      3.内容   生产建议使用Shell脚本作为告警脚本,同时需要日志记录,也可以在Shell脚本中完成,然后可以再使用Shell
继续阅读 »
自定义Zabbix告警,首先需要编写告警脚本,告警脚本需要支持3个参数:
 
     1.告警接收人
     2.主题
     3.内容
 
生产建议使用Shell脚本作为告警脚本,同时需要日志记录,也可以在Shell脚本中完成,然后可以再使用Shell脚本调用其它的脚本。这样可以随时更换告警介质,也可以做好日志记录。
 
 
1.最简单的告警脚本
 
[root@linux-node1 alertscripts]# cat pymail.sh 
#!/bin/bash
echo $1 $2 $3 >> /tmp/alter.txt

 
2.编写告警脚本
#!/usr/bin/python
#coding: utf-8
import smtplib
import sys
from email.mime.text import MIMEText
from email.header import Header
from email.Utils import COMMASPACE

receiver = sys.argv[1]
subject = sys.argv[2]
mailbody = sys.argv[3]
smtpserver = 'smtp.qq.com'
username = 'username'
password = 'passwd'
sender = username

msg = MIMEText(mailbody,'html','utf-8')
msg['Subject'] = Header(subject, 'utf-8')
msg['From'] = username
msg['To'] = receiver

smtp = smtplib.SMTP()
smtp.connect(smtpserver)
smtp.login(username, password)
smtp.starttls()
smtp.sendmail(msg['From'], msg['To'], msg.as_string())
smtp.quit()

 
测试邮件发送
 
[root@linux-node1 alertscripts]# python pymail.py 57459267@qq.com testmail test

 
加入到告警脚本中
 
#!/bin/bash
/usr/local/bin/python /usr/lib/zabbix/alertscripts/pymail.py "$1" "$2" "$3"
收起阅读 »