Linux集群和自动化运维-阿里云开发者社区

开发者社区> 华章出版社> 正文

Linux集群和自动化运维

简介:

d79bd8e8c4ffa9c4d8b60bc9fe7a7d3edd94dcc0


Linux/Unix技术丛书


Linux集群和自动化运维


余洪春 著






图书在版编目(CIP)数据

Linux集群和自动化运维/余洪春著. —北京:机械工业出版社,2016.8

(Linux/Unix技术丛书)

ISBN 978-7-111-54438-8

I. L… II.余… III. Linux操作系统 IV. TP316.89

中国版本图书馆CIP数据核字(2016)第176055号












Linux集群和自动化运维

出版发行:机械工业出版社(北京市西城区百万庄大街22号 邮政编码:100037)

责任编辑:陈佳媛  杨绣国 责任校对:董纪丽

印  刷: 版  次:2016年8月第1版第1次印刷

开  本:186mm×240mm 1/16 印  张:23.25

书  号:ISBN 978-7-111-54438-8   定  价:79.00元

凡购本书,如有缺页、倒页、脱页,由本社发行部调换

客服热线:(010)88379426 88361066 投稿热线:(010)88379604

购书热线:(010)68326294 88379649 68995259 读者信箱:hzit@hzbook.com

版权所有·侵权必究

封底无防伪标均为盗版

本书法律顾问:北京大成律师事务所 韩光/邹晓东






Foreword  推荐序一

在全球“互联网+”的大背景下,互联网创业企业的数量如雨后春笋般大量产生并得到了快速发展!对“互联网+”最有力的支撑就是Linux运维架构师、云计算和大数据工程师,以及自动化开发工程师等!

但是,随着计算机技术的发展,企业对Linux运维人员的能力要求越来越高,这就使得很多想入门运维的新手不知所措,望而却步,甚至努力了很久却仍然徘徊在运维岗位的边缘;而有些已经工作了的运维人员也往往是疲于奔命,没有时间和精力去学习企业所需的新知识和新技能,从而使得个人的职业发展前景大大受限。

本书就是在这样的背景下诞生并致力于为上述问题提供解决方案的,本书是作者余洪春先生10多年来一线工作经验的“再”结晶,此前作者已经出版过Linux集群方向的图书(《构建高可用Linux服务器》),本次出版的书是作者对运维行业的再回馈。

书中不仅涵盖了入门运维人员必须了解的IDC和CDN服务的选型、Linux系统及常见服务的优化实践内容,还有对于企业运维人员需要的大规模集群场景下必备的运维自动化Shell和Python企业开发应用实践案例、热门的自动化运维工具的企业应用实践、大规模集群及高可用的企业案例分享与安全防护等。

本书能够帮助运维人员掌握业内运维实战专家的网站集群的企业级应用经验的精髓,从而以较高的标准胜任各类企业运维的工作岗位,并提升自己的运维职业发展竞争力,值得一读!


—老男孩 老男孩Linux实战运维培训中心总裁

《跟老男孩学Linux运维:Web集群实战》作者





推荐序二 Foreword 

本书作者余洪春先生和我相识于ChinaUnix举办的一次技术交流活动——“千万级PV高性能高并发网站架构与设计交流”,当时他已经在宣传自己的第一本著作——《构建高可用Linux服务器》,该书凝聚并整合了他多年来在一线工作的经验结晶,以至时至今日,该书仍是一本在国内非常经典的运维原创著作,现在已经更新到第三版,这种对技术不断进行完善的坚持及工匠精神让我深深折服。这次能受邀为他的新书《Linux集群和自动化运维》写推荐序,让我倍感荣幸。

本书覆盖了Linux集群服务的核心技术,同时还介绍了基于Python语言构建的主流自动化运维工具,包括Python脚本、Fabric、Ansible等,这些都是DevOps工具元素周期表中最闪亮的内容,也是运维人员必备的技能。本书中分享的案例是余洪春多年实战经验的精华,具有非常高的参考价值及借鉴意义。

书中内容从互联网业务平台构建及自动运维的场景出发,以常见的业务服务为基础,给出了大量的实战案例,这些都是作者在十余年的互联网运维工作中总结出来的宝贵经验,相信会给读者带来不少启发及思考。

更难能可贵的是,作者能从通俗易懂的角度出发,由浅入深地剖析自动运维管理之道。对于不同水平层次的读者来说,都能有效地阅读和吸收,也能根据实际需要各取所需。

最后,感谢余洪春给中国互联网从业者带来这么好的图书,我相信阅读本书的每一位读者都能从中获取提升的能量,为企业及行业做出自己的贡献。


腾讯高级工程师 刘天斯






Preface  前言

为什么要写这本书

笔者从事系统运维和网站架构设计的工作已有10多年,现在在一家外企担任云平台架构师。云计算是现在的主流技术,未来也有很好的发展趋势,云计算的流行对于传统的运维知识体系来说,其实也造成了冲击,有很多读者经常向笔者咨询工作中的困惑,比如从事系统运维工作3~5年后就不知道该如何继续学习和规划自己的职业生涯了。因此笔者想通过此书,跟大家分享一下自己的工作经验和心得(包括传统运维和云平台运维工作的区别与对比),以期解决大家在工作中的困惑。本书提供了大量项目实践和线上案例,希望能让大家迅速了解Linux运维人员的工作职责,快速进入工作状态并找到成长方向。希望大家通过阅读此书,能够掌握Linux系统集群和自动化运维及网站架构设计的精髓,从而能够轻松愉快地工作,并提升自己的职业技能,这就是笔者写作此书的初衷。

运维架构师之路

在成为运维架构师之前,笔者从事过很长一段时间的系统集成、运维和管理工作,在CDN门户网站、电子广告、电子商务领域也有不少的沉淀和积累,在之前的《构建高可用Linux服务器》一书中已经跟大家分享了很多跟Linux集群有关的知识。笔者目前的主要工作职责是维护和优化公司的DSP电子广告业务平台,主要方向是云计算和大数据方面。需要维护的数据中心和机器数量非常之多,所以自动化运维和DevOps是目前的主要工作方向,此外,也会涉及网站架构设计及调优工作,因此在此书中特意将这部分工作经验分享出来,希望大家能从中学到新的知识体系,借以提升自己的职业技能。

读者对象

本书适合以下读者阅读。

中高级系统管理员

系统架构设计师

高级程序开发人员

运维开发工程师

如何阅读本书

本书是笔者对实际工作中积累的技术和经验所做的总结,涉及大量的知识点和专业术语。全书总共分为三大部分,第一部分包含第1章和第2章,主要讲解进行系统架构设计的软硬件环境,以及生产环境下的Shell脚本和Python脚本。其中,第2章的内容是以Shell为主,Python为辅,Shell部分讲得比较详细,Python部分需要重点关注的地方也有所提及。之所以这样安排,主要是考虑到大多数搞开发的读者或DevOps工程师都是Java程序员出身,对Shell脚本语言不是很熟悉。第二部分包含第3章、第4章和第5章,主要讲自动化运维,包括Fabric、Ansibel和Puppet三大工具,大家可以结合自己的实际环境来选择对应的工具。第三部分包含第6章、第7章和第8章,主要讲的是Linux集群和网站架构设计,特别是第8章,分别以百万PV、千万PV及亿级PV的网站为例来详细说明网站系统架构设计的相关技术,然后细分五层来解说网站的架构,并指出了设计网站的压力及关注点所在。

大家可以根据自己的职业发展和工作需求来选择不同的章节进行阅读或学习。

关于本书中的配置文件、Shell脚本和Python脚本的编号,这里也略作说明,比如1.5.3节中有1.sh,表示这是1.5.3节的第一个Shell脚本;如果是2.py,则表示是1.5.3节的第二个Python脚本;其他依此类推,在哪个章节中出现的配置文件或脚本就在哪个章节中寻找,这样对照起来阅读理解会比较方便。此外,书中多次出现的Nginx配置文件nginx.conf也在对应的章节里。本书相关的GitHub地址为http://github.com/yuhongchun/automation。

勘误

尽管笔者花费了大量的时间和精力来核对文件和语法,但书中难免还会存在一些错误和纰漏,如果大家发现有任何问题,都请及时反馈给我,相关信息可以发到个人邮箱yuhongchun027@gmail.com。尽管无法保证对于每一个问题都会有一个正确答案,但我肯定会努力回答并且指出一个正确的方向。

致谢

感谢爱女媛媛的出生,你的降临是上天赐给我的最好礼物,是我进行写作的源泉和动力。

感谢我的家人,他们在生活上对我的照顾无微不至,让我有更多的精力和动力去工作和创作。

感谢好友三宝这么多年来对我的信任和支持,从始至终一直都在支持和信任我。

感谢机械工业出版社华章公司的编辑杨福川和杨绣国,在你们的信任、支持和帮助下,我才能如此顺利地完成全部书稿。

感谢好友老男孩和刘天斯,闲暇之余和你们一起交流开源技术和发展趋势,也是一种享受。

感谢Linux之父—Linus Torvalds,他不仅创造了Linux系统,而且还创造了Git这么神奇的版本管理软件。


余洪春(抚琴煮酒)

中国,武汉







目录  Contents


推荐序一

推荐序二

前  言

第1章 系统架构设计的构建基础1

1.1 网站架构设计相关术语1

1.1.1 什么是HTTP 1.11

1.1.2 什么是Web 2.02

1.1.3 软件开发C/S结构与B/S结构的区别3

1.1.4 评估网站性能的专业术语5

1.2 IDC机房的选择及CDN的选型6

1.3 如何根据服务器应用选购服务器7

1.4 CentOS 6.4 x86_64最小化安装后的优化13

1.4.1 系统的基础优化13

1.4.2 优化Linux下的内核TCP参数以提高系统性能19

1.4.3 CentOS 6.4 x86_64系统最小化优化脚本22

1.4.4 Linux下CPU使用率与机器负载的关系与区别23

1.5 MySQL数据库的优化25

1.5.1 服务器物理硬件的优化25

1.5.2 利用tuning-primer脚本来调优MySQL数据库25

1.6 小结28

第2章 生产环境下的Shell和Python脚本29

2.1 Shell和Python语言的简单介绍29

2.2 Shell编程基础30

2.2.1 Shell脚本的基本元素30

2.2.2 Shell特殊字符31

2.2.3 变量和运算符31

2.3 Shell中的控制流结构42

2.4 sed的基础用法及实用示例45

2.4.1 sed的基础语法格式46

2.4.2 sed的用法示例51

2.5 awk的基础用法及实用示例56

2.6 生产环境下的Shell和Python脚本分类61

2.6.1 备份类脚本62

2.6.2 统计类脚本66

2.6.3 监控类脚本69

2.6.4 开发类脚本72

2.6.5 自动化类脚本78

2.7 小结80

第3章 轻量级自动化运维工具Fabric详解81

3.1 Python语言的应用领域81

3.2 选择Python的原因83

3.3 Python的版本说明83

3.4 增强的交互式环境IPython84

3.5 Python(x,y)介绍85

3.6 轻量级自动化运维工具Fabric介绍86

3.6.1 Fabric的安装87

3.6.2 命令行入口fab命令详解88

3.6.3 Fabric的核心API88

3.7 Fabric应用实例92

3.7.1 开发环境中的Fabric应用实例92

3.7.2 线上环境中的Fabric应用实例93

3.8 小结96

第4章 自动化部署管理工具Ansible简介97

4.1 YAML语言介绍99

4.2 Ansible的安装步骤 101

4.3 利用ssh-keygen设置SSH无密码登录105

4.4 Ansible常用模块介绍107

4.5 playbook介绍121

4.6 角色126

4.7 Jinja2过滤器132

4.8 小结136

第5章 自动化配置管理工具Puppet137

5.1 Puppet的基本概念及介绍137

5.1.1 Puppet简介137

5.1.2 学习Puppet应该掌握Ruby基础138

5.1.3 Puppet的基本概念及工作流程介绍138

5.2 安装Puppet前的准备工作140

5.3 Puppet的详细安装步骤141

5.4 Puppet的简单文件应用 145

5.5 Puppet的进阶操作152

5.5.1 如何同步Puppet-Client端上的常用服务152

5.5.2 如何在Puppet-Client端自动安装常用的软件包153

5.5.3 如何自动同步Puppet-Client端的yum源153

5.5.4 如何根据不同名字的节点机器推送不同的文件155

5.5.5 如何根据节点机器名选择性地执行Shell程序158

5.5.6 如何快速同步Puppet-Server端的www目录文件160

5.5.7 如何利用ERB模板来自动配置Apache虚拟主机165

5.5.8 如何利用ERB模板来自动配置Nginx虚拟主机168

5.6 Puppet的负载均衡方式172

5.7 用GitHub来管理Puppet配置文件173

5.8 小结176

第6章 Linux防火墙及系统安全篇177

6.1基础网络知识177

6.1.1OSI网络参考模型177

6.1.2TCP/IP三次握手的过程详解178

6.1.3Socket应用及其他基础网络知识181

6.2Linux防火墙的概念182

6.3Linux防火墙在企业中的应用183

6.4Linux防火墙的语法184

6.5iptables的基础知识188

6.5.1iptables的状态state188

6.5.2iptables的conntrack记录190

6.5.3关于iptables模块的说明191

6.5.4iptables防火墙初始化的注意事项192

6.5.5如何保存运行中的iptables规则192

6.6如何流程化编写iptables脚本193

6.7学习iptables应该掌握的工具 196

6.7.1 命令行的抓包工具TCPDump196

6.7.2图形化抓包工具Wireshark197

6.7.3强大的命令行扫描工具Nmap200

6.8iptables简单脚本:Web主机防护脚本203

6.9线上生产服务器的iptables脚本204

6.9.1安全的主机iptables防火墙脚本205

6.9.2自动分析黑名单及白名单的iptables脚本207

6.9.3利用recent模块限制同一IP的连接数210

6.9.4利用DenyHosts工具和脚本来防止SSH暴力破解214

6.10TCP_Wrappers应用级防火墙的介绍和应用218

6.11工作中的Linux防火墙总结220

6.12Linux服务器基础防护知识221

6.13Linux服务器高级防护知识222

6.14如何防止入侵222

6.15小结223

第7章 Linux集群及项目案例分享224

7.1负载均衡高可用核心概念及常用软件224

7.1.1什么是负载均衡高可用224

7.1.2以F5 BIG-IP作为负载均衡器225

7.1.3以LVS作为负载均衡器226

7.1.4以Nginx作为负载均衡器230

7.1.5以HAProxy作为负载均衡器231

7.1.6高可用软件Keepalived232

7.1.7高可用软件Heartbeat233

7.1.8高可用块设备DRBD233

7.1.9四、七层负载均衡工作流程对比235

7.2负载均衡关键技术237

7.2.1什么是Session237

7.2.2什么是Session共享237

7.2.3什么是会话保持238

7.3负载均衡器的会话保持机制239

7.3.1LVS的会话保持机制239

7.3.2Nginx负载均衡器中的ip_hash算法244

7.3.3HAProxy负载均衡器的source算法244

7.3.4服务器健康检测技术249

7.4Linux集群的项目案例分享250

7.4.1案例分享一:用Nginx+Keepalived实现在线票务系统250

7.4.2案例分享二:企业级Web负载均衡高可用之Nginx+Keepalived253

7.4.3案例分享三:Nginx主主负载均衡架构265

7.4.4案例分享四:生产环境下的高可用NFS文件服务器270

7.4.5案例分享五:生产环境下的MySQL DRBD双机高可用280

7.4.6案例分享六:生产环境下的MySQL数据库主从同步293

7.4.7案例分享七:HAProxy双机高可用方案之HAProxy+Keepalived303

7.4.8案例分享八:巧用DNS轮询做负载均衡308

7.5软件级负载均衡器的特点介绍与对比313

7.6网站系统架构设计图315

7.7小结316

第8章 浅谈网站系统架构设计318

8.1网站架构设计规划预案318

8.1.1利用经验,合理设计318

8.1.2规划好网站未来的发展319

8.1.3合理选用开源软件方案319

8.1.4机房及CDN选型319

8.1.5节约成本320

8.1.6安全备份320

8.2百万级PV高可用网站架构设计321

8.3千万级PV高性能高并发网站架构设计323

8.4亿级PV高性能高并发网站架构设计327

8.5细分五层解说网站架构333

8.6小结335

附录A HAProxy 1.4的配置文档336

附录B rsync及inotify在工作中的应用343

附录C 用Supervisor批量管理进程355


第1章

系统架构设计的构建基础

作为一名系统架构设计师,会面临着很多系统方面的架构设计工作,比如电子商务系统、CDN(内容分发网络)大型电子广告平台和DSP电子广告系统的运维方案确定及平台架构设计等,此外,还会涉及核心业务的系统在线优化及升级等工作。在以上这些工作中,又将包括多项选择:比如是考虑自建CDN,还是租赁CDN系统;公司的业务系统所在的机房是考虑自建机房、托管机房,还是云计算平台,而如果选择托管机房,又会有更多的细节需要考虑,比如是选择电信机房、双线机房还是BGP机房,服务器应该如何选型,选择哪种操作系统等,这个时候系统架构设计师的经验和作用就体现出来了。他们应该在系统网站实施的初期就做好项目的成本预算和风险规避,并对系统的高可用及扩展性进行细致权衡,这些也是其工作职责所在。当然,在了解上述这些之前,首先应该了解一些网站架构设计相关的专业术语,下面就一起来看看。

1.1 网站架构设计相关术语

1.1.1 什么是HTTP 1.1

HTTP 1.1(Hypertext Transfer Protocol Version 1.1),即超文本传输协议-版本1.1,跟版本1.0是有区别的。

针对HTTP 1.0中TCP连接不能重复利用的情况,HTTP1.1采用了效率更高的持续连接机制,即客户端和服务器建立TCP连接以后,后续相关联的HTTP请求可以重复利用已经建立起来的TCP连接。

HTTP 1.1是用来在Internet上传送超文本的传送协议。它是运行在TCP/IP协议族之上的HTTP应用协议,它可以使浏览器更加高效,并减少网络传输。任何服务器除了包括HTML文件以外,都还有一个HTTP驻留程序,用于响应用户请求。如果浏览器是HTTP客户,在向服务器发送请求时,向浏览器中输入一个开始文件或点击一个超级链接,浏览器就向服务器发送HTTP请求,此请求被送往由URL指定的IP地址。驻留程序接收到请求,在进行必要的操作后就会回送所要求的文件。

HTTP 1.1支持持续连接。通过这种连接,就有可能在建立一个TCP连接后,发送请求并得到回应,然后发送更多的请求并得到更多的回应。由于把建立和释放TCP连接的开销分摊到了多个请求上,因此对于每个请求而言,由于TCP连接而造成的相对开销就被大大地降低了。而且,还可以发送流水线请求,也就是说在发送请求1的回应到来之前就发送请求2。也可以认为,一次连接发送多个请求,由客户机确认是否关闭连接,而服务器会认为这些请求分别来自于不同的请求。

1.1.2 什么是Web 2.0

Web 2.0,指的是利用Web的平台,由用户主导而生成内容的互联网产品模式,为了区别由网站雇员主导生成内容的传统网站而定义为Web 2.0。Web 1.0的盈利模式都基于一个共同点,即巨大的点击流量,无论是早期融资还是后期获利,依托的都是众多的用户和点击率,以点击率为基础融资上市或开展增值服务,充分体现了互联网的眼球经济色彩,例如早期的新浪、搜狐和网易等。

Web 2.0是资源平等的体现。Web 2.0的应用可以让人了解到目前万维网正在进行的一场改变—从一系列网站到一个成熟的、为最终用户提供网络应用的服务平台。这种概念的支持者期望Web 2.0服务在很多用途上能最终取代桌面计算机应用。虽然Web 2.0并不是一个技术标准,但是它包含了技术架构及应用软件。它的特点是鼓励信息的最终利用者通过分享,使得可供分享的资源变得更加丰富;相反的,过去网上的各种分享方式则显得支离破碎。

Web 2.0是网络运用的新时代,网络成为了新的平台,内容因为每位用户的参与而产生,参与所产生的个人化内容,借由人与人(P2P)的分享,形成了现在的Web 2.0世界。

Web 2.0的主要特点和基于这些特点所产生的具有代表性的服务如下。

1.博客

博客(Blog)是Web 2.0最早期的服务之一,可使任何参与者拥有自己的专栏,成为网络内容的产生源,进而形成微媒体,为网络提供文字、图片、声音或视频信息。

2.内容源

内容源(RSS)是伴随博客产生的简单文本协议,将博客产生的内容进行重新格式化输出,从而将内容从页面中分离出来,便于同步到第三方网站或提供给订阅者进行阅读。

3. Wiki

是一个众人协作的平台,方便编写百科全书、词典等。Wiki指的是一种超文本系统,这种超文本系统支持面向社区的协作写作,例如百度百科和维基百科。

4.参与评论与评分的Digg机制

Web 2.0最显著的特点之一是分享机制和去中心化,Digg机制为更多的网络用户提供了参与网络建设的机制,无须进行内容贡献或创作,只要用户对网络内容进行评分或点评,即可参与到网络内容的建设过程当中。

5.美味书签

美味书签(Delicious)不同于个人博客,用户可根据自己的喜好进行网络内容的收藏与转载,并将自己的收藏或转载整理成列表,分享给更多的用户,从而在网络上起到信息聚合与过滤的作用。

6.社会化网络

社会化网络(SNS)从原有的以网站、内容为中心,转为侧重于以人与人之间的关联为中心,网络上每一个节点所承载的不再是信息,而是以具体的自然人为节点,形成的新型互联网形态。

7.微博

微博(Microblog)作为博客的精简版,有较为严格的字数限制和政治立场限制。有更简单的发布流程和更随意(被限制的话题、领域)的写作方式,使得参与到网络内容贡献中的门槛降低,更大程度地推动了网络内容建设和个体信息贡献。

8.基于位置信息的服务

基于位置信息的服务(LBS)是集地理信息系统(GIS)、微博(Twitter)和移动设备(Mobile)及A-GPS定位服务于一体的增强型微博系统,其主导思想是每一条信息除了利用时间为索引,还加入了地理经纬度的索引,从而实现不仅可以通过时间对信息进行筛选,还可以利用地理坐标对信息进行合理的筛选。

9.即时通信

即时通信(IM)软件可以说是目前中国网上用户使用率最高的软件。聊天一直是网民们上网的主要活动之一,网上聊天的主要工具已经从初期的聊天室、论坛变为以QQ和Skype为主要代表的即时通信软件。

1.1.3 软件开发C/S结构与B/S结构的区别

C/S结构是大家熟知的软件系统体系结构,即Client/Server(客户机/服务器)结构,它通过将任务合理地分配到Client端和Server端,来降低系统的通信开销,不过需要安装客户端才可进行管理操作。B/S结构,即Browser/Server(浏览器/服务器)结构,是随着Internet技术的兴起,对C/S结构的一种变化或改进的结构。在这种结构下,用户界面可完全通过WWW浏览器来实现。像QQ、Skype这类即时通信软件就属于C/S结构;而像百度、Google这样的搜索引擎就属于B/S结构。

随着计算机技术的不断发展与应用,计算模式从集中式转向了分布式,尤为典型的是C/S结构。两层结构C/S模式,在20世纪80年代及90年代初得到了大量的应用,最直接的原因是可视化开发工具的推广。之后,它开始向三层结构发展。近年来,随着网络技术的不断发展,尤其是基于Web的信息发布和检索技术、Java计算技术及网络分布式对象技术的飞速发展,导致了很多应用系统的体系结构从C/S结构向更加灵活的多级分布结构演变,使得软件系统的网络体系结构跨入一个新阶段,即B/S体系结构。基于Web的B/S模式其实也是一种客户机/服务器模式,只不过它的客户端是浏览器。为了区别于传统的C/S模式,才特意将其称为B/S模式的。了解这些结构的特征,对于系统的选型而言是很关键的。

下面是C/S结构与B/S结构的特点分析。

1.系统的性能

在系统的性能方面,B/S占有优势的是其异地浏览和信息采集的灵活性。任何时间、任何地点、任何系统,只要可以使用浏览器上网,就可以使用B/S系统的终端。

不过,采用B/S结构时,客户端只能完成浏览、查询、数据输入等简单功能,绝大部分工作由服务器承担,这就使得服务器的负担很重。采用C/S结构时,客户端和服务器端都能够处理任务,这虽然对客户机的要求较高,但因此可以减轻服务器的压力。而且,由于客户端使用浏览器,使得网上发布的信息必须是以HTML格式为主,其他格式的文件则多半是以附件的形式来存放的。而且HTML格式文件(也就是Web页面)不便于编辑修改,给文件的管理带来了许多不便。

2.系统的开发

C/S结构是建立在中间件产品基础之上的,要求应用开发者自己去处理事务管理、消息队列、数据的复制和同步、通信安全等系统级的问题。这对应用开发者提出了较高的要求,而且还会迫使应用开发者投入很多精力来解决应用程序以外的问题,这使得应用程序的维护、移植和互操作变得复杂。如果客户端是在不同的操作系统上,那么C/S结构的软件还需要开发不同版本的客户端软件。

但是,与B/S结构相比,C/S技术的发展历史更为“悠久”。从技术成熟度及软件设计、开发人员的掌握水平来看,C/S技术应是更成熟、更可靠的。

3.系统的升级维护

C/S系统的模块中只要有一部分发生改变,就会关联到其他模块的变动,这会使得系统的升级成本比较高。B/S与C/S处理模式相比,则大大简化了客户端,只要客户端机器能上网就可以。对于B/S而言,开发、维护等几乎所有的工作也都集中在服务器端,当企业对网络应用进行升级时,只需更新服务器端的软件就可以了,这就降低了异地用户进行系统维护与升级的成本。如果客户端的软件系统升级比较频繁,那么B/S架构的产品优势就更明显—所有的升级操作只需要针对服务器进行即可,这对那些点多面广的应用是很有价值的,例如一些招聘网站就需要采用B/S模式,客户端分散,且应用简单,只需要进行简单的浏览和少量信息的录入即可。

在系统安全维护上,B/S则略显不足,B/S结构尤其得考虑数据的安全性和服务器的安全性,毕竟现在的网络安全系数并不高。以OA(办公自动化)软件为例,B/S结构要实现办公协作过程中复杂的工作流控制与安全性控制,还有很多技术上的难点。因此,当前虽然出现了B/S结构的OA系统产品,但尚未大范围推广。

1.1.4 评估网站性能的专业术语

1. PV

PV(Page View)即访问量,中文翻译为页面浏览,即页面浏览量或点击量,用户每刷新一次就会被计算一次。PV的具体度量方法就是从浏览器对网络服务器发出一个请求(Request),网络服务器接到这个请求后,会将该请求对应的一个网页(Page)发送给浏览器,从而产生一个PV。在这里只要是请求发送给了浏览器,无论这个页面是否完全打开(下载完成),那么都被计为1个PV。PV反映的是浏览某网站的页面数,所以每刷新一次也算一次,也就是说PV与UV(独立访客)的数量成正比,但PV并不是页面的来访者数量,而是网站被访问的页面数量。

2. UV

UV(Unique Visitor)即独立访问,访问网站的一台电脑客户端为一个访客,如果以天为计量单位,程序会统计00:00至24:00时间段内的电脑客户端。相同的客户端只被计算一次。一个电脑客户端可能有多个不同的自然人访问,但也只记一个UV,通过不同的技术方法来记录,实际会有误差。如果企业内部通过NAT技术共享上网,那么出去的公网IP有且只有一个,这个时候在程序里面统计的话,也只能算是一个UV。

3.并发连接数

当一个网页被浏览,服务器就会和浏览器建立连接,每个连接表示一个并发。如果当前网页的页面中包含很多图片,图片并不是一个一个显示的,服务器会产生多个连接同时发送文字和图片以提高浏览速度。网页中的图片越多,那么服务器的并发连接数(Concurrent TCP Connections)就会越多。我们一般以此值作为衡量单台Web机器性能的参数。现在Nginx在网站中的应用比例非常大,可以参考Nginx的活动并发连接数。

4.每秒查询率QPS

QPS(Query Per Second)是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准,在因特网上,作为域名系统服务器的机器其性能经常用每秒查询率来衡量。对应的是Fetches/Sec,即每秒的响应请求数,也称为最大吞吐能力。对于系统而言,QPS数值是一个非常重要的参数,它是综合反映系统最大吞吐能力的衡量标准。它反映的不仅仅是Web层面的,还有缓存、数据库方面的,它反映的是系统的综合处理能力。

参考文档http://baike.baidu.com/view/733.htm

参考文档http://www.hyokay.com/news/industry/41.html

1.2 IDC机房的选择及CDN的选型

如果自己的业务网站中含有大量的图片和视频类文件,为了加快客户端的访问速度,同时为了减缓对真正的核心机房的服务压力,并且提升用户体验,建议在前端最好采用CDN缓存加速方案。

CDN(Content Delivery Network),即内容分发网络。其目的是通过在现有的Internet中增加一层新的网络架构,将网站的内容发布到最接近用户的网络“边缘”,使用户可以就近取得所需的内容,提高用户访问网站的响应速度。CDN缓存加速方案一般有如下几种方式。

租赁CDN:中小型网站直接购买服务就好,现在CDN已经进入按需付费的云计算模式了,性价比是可以准确计算的。

自建CDN:这种方案的成本就有点大了,为了保证良好的缓存效果,必须在全国机房布点,还要自建智能Bind系统,搭建大型网站时推荐采用此种方案,专业的视频网站或图片网站一般会考虑采用此种方案。

IDC机房的选择一般也有几种类型。

单电信IDC机房:这种类型一般业务模式比较固定,访问量也不是很大,适合新闻类网站或政务类网站。如果网站的PV流量持续增加的话,则建议后期采用租赁CDN的方式解决非电信用户访问网站速度过慢的问题。

双线IDC机房:由于国内两大网络(电信和网通)之间存在互联互通的问题,导致电信用户访问网通网站或网通用户访问电信网站速度很慢,因此就产生了双线机房、双线服务器、双线服务器托管和双线服务器租用服务。双线机房实际是一个机房有电信和网通两条线路。双线机房通过内部路由器设置,以及BGP自动路由的分析,可实现电信用户访问电信线路,网通用户访问网通线路,这样就可实现电信网通的快速访问。

BGP机房:BGP(边界网关协议)是用来连接Internet独立系统的路由选择协议。它是Internet工程任务组制定的一个加强的、完善的、可伸缩的协议。BGP4支持CIDR寻址方案,该方案增加了Internet上的可用IP地址数量。BGP是为取代最初的外部网关协议EGP而设计的。它也被认为是一个路径矢量协议。采用BGP方案来实现双线路互联或多线路互联的机房,则称为BGP机房。对于用户来说,选择BGP机房可以实现网站在各运营商线路之间互联互通,使得所有互联运营商的用户访问网站都很快,且更加稳定,不用担心全国各地因线路问题带来的访问速度快慢不一的问题,这也是传统双IP双线机房无法相比的优势。在条件允许的情况下,服务器的租用和托管可以尽量选择BGP机房,因为会带给用户最优的访问体验。

现在云计算服务也非常流行,目前首推的就是亚马逊云(AWS)和阿里云。

对于我们来说,云计算服务提供的产品能让我们的研究发团队专注于产品开发本身,而不是购买、配置和维护硬件等繁杂的工作,还可以减少初始资金的投入。我们主要采用亚马逊云的EC2/EBS/S3服务,其实Amazon EC2主机提供了多种适用于不同使用案例的实例类型以供选择。实例类型由 CPU、内存、存储和网络容量组成了不同的组合,可让我们灵活地为其选择适当的资源组合。

云计算特别适合两类网站:在某些日期或某些时间段流量会激增的网站,比如竞标业务机器,用户会集中在某些时段进行竞价,因此在这些时间段使用的Instance数量可能是白天的几倍甚至几十倍。也就是说,此时段内瞬间可能要开启很多实例处理,处理完毕后立刻终止。EC2 Instance是可以按照运行的小时数来进行收费的。像笔者公司的线上系统,经常运行着很多特殊业务的Spot Instance,以小时计费,完成任务后立即终止。

1.3 如何根据服务器应用选购服务器

无论物理服务器是选用IDC托管还是AWS EC2云主机(以下为了简略说明,将它们统称为服务器),我们都要面临一个问题,那就是选择服务器的硬件配置,选购硬件配置时要根据服务器的应用需求而定。因为只通过一台服务器是无法满足所有的需求,并解决所有的问题的。在设计网站的系统架构之前,应该从以下方面考虑如何选购服务器:

服务器要运行什么应用。

需要支持多少用户访问。

需要多大空间来存储数据。

业务有多重要。

服务器网卡方面的考虑。

安全方面的考虑。

机架安排是否合理化。

服务器的价格是否超出了预算。

1.服务器运行什么应用

这是选购服务器时首先需要考虑的问题,通常是根据服务器的应用类型(也就是用途),来决定服务器的性能、容量和可靠性需求。下面将按照负载均衡、缓存服务器、前端服务器、应用程序服务器、数据服务器和Hadoop分布式计算的常见基础架构来讨论。

负载均衡端:除了网卡性能以外,其他方面对服务器的要求比较低,如果选用的是LVS负载均衡方案,那么它会直接将所有的连接要求都转给后端的Web应用服务器,因此建议选用万兆网卡。如果选用的是HAProxy负载均衡器,由于它的运行机制跟LVS不一样,流量必须双向经过HAProxy机器本身,因此对CPU的运行能力会有要求,也建议选用万兆网卡。如果选用的是AWS EC2机器,则推荐使用m3.xlarge实例类型(m3类型提供计算、内存和网络资源的平衡,因此是很多应用程序的良好选择)。另外,AWS官方也推出了负载均衡服务产品,即Elastic Load Balancing,它具有DNS故障转移和Auto Scalling的功能。

缓存服务器:主要是Varnish和redis,对CPU及其他方面的性能要求一般,但在内存方面的要求会尽量多些。笔者曾为了保证预算,在双核(r3.large)机器上运行了4个redis实例,AWS官方也建议将此内存优化型实例应用于高性能数据库、分布式内存缓存、内存中分析、基因组装配和分析,以及 SAP、Microsoft SharePoint 和其他企业级应用程序的较大部署。

应用服务器:由于它承担了计算和功能实现的重任,因此需要为基于Web架构的应用程序服务器(Application Server)选择足够快的服务器,另外应用程序服务器可能需要用到大量的内存,尤其是基于Windows基础架构的Ruby、Python、Java服务器,这一类服务器至少需要使用单路至强的配置;笔者公司线上的核心业务机器选用的AWS c3.xlarge类型。至于可靠性问题,如果你的架构中只有一台应用服务器,那这台服务器肯定要足够可靠才行,RAID是绝对不能被忽视的选项。但如果有多台应用服务器,并设计了负载均衡机制,具有冗余功能,那就不必过于担心了。

c3.xlarge EC2主机属于计算优化型(Compute Optimized),也就是CPU加强型。这种类型的CPU/内存比例比较大,适合于计算密集型业务,它包含c1和c3系列。其实例除了较旧的两个c1系列(c1.medium和c1.xlarge)是采用普通磁盘作为实例存储以外,其他的(也就是c3系列的)全部都以SSD作为实例存储,其中最高档次的c3.8xlarge(32核心108个计算单元)的网络性能明确标注为10G bit/s,c3系列被认为是最具性价比的类型。

特殊应用:除了用于Web架构中的应用程序之外,如果服务器还要处理流媒体视频编码、服务器虚拟化、媒体服务器,或者作为游戏服务器(逻辑、地图、聊天)运行,那么对CPU和内存的需求同样会比较高,至少要考虑四核以上的服务器。

公共服务:这里指的是邮件服务器、文件服务器、DNS服务器、域控服务器等。通常会部署两台DNS服务器以互相备份,域控主服务器也会拥有一台备份服务器(专用的或非专用的),所以对于可靠性,无须过于苛刻。至于邮件服务器,至少需要具备足够的硬件可靠性和容量大小,这主要是对邮件数据负责,因为很多用户没有保存和归档邮件数据的习惯,待其重装系统后,就会习惯性地到服务器上重新下载相应的数据。至于性能问题,则应评估用户数量后再做决定。另外,考虑到它的重要性,建议尽量选择稳定的服务器系统,比如Linux或BSD系列。

数据库服务器:数据库对服务器的要求也是最高、最重要的。无论你使用的是MySQL、SQL Server还是Oracle,一般情况下,都需要有足够快的CPU、足够大的内存、足够稳定可靠的硬件。可直接采用DELL PowerEdge R710或HP 580G5,CPU和内存方面也要尽可能最大化,如果预算充分,建议用固态硬盘做RAID 10,因为数据库服务器对硬盘的I/O要求是最高的。

Hadoop分布式计算:这里建议选用密集存储实例—D2实例,它拥有高频率 Intel Xeon E5-2676v3(Haswell)处理器、高达48TB的本地存储、具备高磁盘吞吐量,并支持 Amazon EC2 增强型联网。它适合于大规模并行处理数据仓库、MapReduce和Hadoop分布式计算、分布式文件系统、网络文件系统、日志或数据处理等应用。

更多关于AWS EC2的实例类型请参考:https://aws.amazon.com/cn/ec2/instance-types/。

2.服务器需要支持多少用户访问

服务器就是用来给用户提供某种服务的,所以使用这些服务的用户同样是我们必须考虑的因素,可以从下面几个具体的方面进行评估:

有多少注册用户。

正常情况下有多少用户会同时在线访问。

每天同时在线访问的最高峰值大概是多少。

一般在项目实施之前,客户方面会针对这些问题给出一个大致的结果,但设计要尽量更充分和具体,同时,还要对未来的用户增长做一个尽可能准确的预测和规划,因为服务器可能会支持越来越多的用户,所以在进行网站或系统架构设计时要让机器能够灵活地扩展。

3.需要多大空间来存储数据

关于这个问题需要从两个方面来考虑,一方面是有哪些类别的数据,包括:操作系统本身占用的空间,安装应用程序所需要的空间,应用程序所产生的数据、数据库、日志文件、邮件数据等,如果网站是Web 2.0的,还要计算每个用户的存储空间;另一方面是从时间轴上来考虑,这些数据每天都在增长,至少要为未来两三年的数据增长做个准确的预算,这就需要软件开发人员和业务人员一起来提供充分的信息了。最后将计算出来的结果乘上1.5左右的系数,以方便维护的时候做各种数据的备份和文件转移操作。

4.我的业务有多重要

这需要根据自身的业务领域来考虑,下面举几个简单的例子,帮助大家了解这些服务器对可靠性、数据完整性等方面的要求:

如果服务器是用来运行一个WordPress博客的,那么,一台酷睿处理器的服务器、1GB的内存,外加一块160GB的硬盘就足够了(如果是AWS EC2主机,可以考虑t2.micro实例类型)。就算服务器出现了一点硬件故障,导致几个小时甚至一两天不能提供访问,生活也会照常继续。

如果服务器是用作测试平台的,那么就不会如生产环境那样对可靠性有极高的要求,所需要的可能只是做好例行的数据备份即可,若服务器宕机,只要能在当天把问题解决掉就可以了。

如果是一家电子商务公司的服务器,运行着电子商务网站平台,当硬件发生故障而导致宕机时,你需要对以下“危言耸听”的后果做好心理准备:投诉电话被打爆、顾客大量流失、顾客要求退款、市场推广费用打水漂、员工无事可干、公司运营陷入瘫痪状态、数据丢失。事实上,电子商务网站一般是需要365×24小时不间断运行和监控的,而且要有专人轮流值守,并且要有足够的备份设备,每天还要有专人负责检查。

如果是大型广告类或门户类网站,那么建议选择CDN系统。由于它具有提高网站响应速度、负载均衡、有效抵御DDoS攻击等特点,相对而言,每个节点都会有大量的冗余。

这里其实只是简单地讨论下业务对服务器硬件可靠性的要求。要全面解决这个问题,不能只考虑单个服务器的硬件,还需要结合系统架构的规划设计。

在回答了以上问题后,接下来就可以决定下面这些具体的选项了。

(1)选择什么CPU

回忆一下上面关于“服务器运行什么应用”和“需要支持多少用户访问”两个方面的考虑,这将帮助我们选择合适的CPU。毫无疑问,CPU的主频越高,其性能也就越高;两个CPU要比一个CPU来得更好;至强(Xeon)肯定比酷睿(Core)的性能更强。但究竟怎样的CPU才是最合适的呢?下面提供了一些常见情况下的建议:

如果业务刚刚起步,预算不是很充足,建议选择一款经典的酷睿服务器,这可以帮你节约大量的成本。而且,以后还可以根据业务发展的情况,随时升级到更高配置的服务器。

如果需要在一台服务器上同时运行多种应用服务,例如基于LNMP架构的Web网站,那么一个单核至强(例如X3330)或新一代的酷睿I5(双核四线程)将是最佳的选择。虽然从技术的角度来说,这并不是一个好主意,但至少能节约一大笔成本。

如果服务器要运行MySQL或Oracle数据库,而且目前有几百个用户同时在线,未来还会不断增长,那么至少应该选择安装一个双四核服务器。

如果需要的是Web应用服务器,双四核基本就可以满足要求了。

(2)需要多大的内存

同样,“服务器运行什么应用”和“需要支持多少用户访问”两方面的考虑也将有助于我们选择合适的内存容量。相比于CPU,笔者认为内存(RAM)才是影响性能的最关键因素。因为在很多正在运行的服务器中,CPU的利用率一般都在10%~30%之间,甚至更低。但由于内存容量不够而导致服务器运行缓慢的案例比比皆是,如果服务器不能分配足够的内存给应用程序,那么应用程序就需要通过硬盘接口缓慢地交换读写数据了,这将导致网站慢得令人无法接受。内存的大小主要取决于服务器的用户数量,当然也和应用软件对内存的最低需求和内存管理机制有关,所以,最好由程序员或软件开发商给出最佳的内存配置建议。下面同样给出了一些常见应用环境下的内存配置建议:

无论是Apache还是Nginx服务器,一般情况下Web前端服务器都不需要配置特别高的内存,尤其是在集群架构中,4GB的内存就已经足够了。如果用户数量持续增加,我们才会考虑使用8GB或更大的内存。单Apache Web机器,在配置了16GB的内存后,可以抗6000个并发链接数。

对于运行Tomcat、Resin、WebLogic的应用服务器,8GB内存应该是基准配置,更准确的数字需要根据用户数量和技术架构来确定。

数据库服务器的内存由数据库实例的数量、表大小、索引、用户数量等来决定,一般建议配置16GB以上的内存,笔者公司在许多项目方案中使用了24GB到48GB的内存。

诸如Postfix和Exchange这样的邮件服务器对内存的要求并不高,1GB~2GB就可以满足了。

还有一些特殊的服务器,需要为之配置尽可能大的内存容量,比如配置有Varnish和Memcached的缓存服务器等。

若是只有一台文件服务器,1GB的内存可能就足够了。

事实上,由于内存技术在不断进化,价格也在不断降低,因此才得以近乎奢侈地讨论4GB、8GB、16GB这些曾经不可想象的内存容量。然而,除了花钱购买内存来满足应用程序的“贪婪”之外,系统优化和数据库优化仍然是我们需要重视的问题。

(3)需要怎样的硬盘存储系统

硬盘存储系统的选择和配置是整个服务器系统里最复杂的一部分,需要考虑硬盘的数量、容量、接口类型、转速、缓存大小,以及是否需要RAID卡、RAID卡的型号和RAID级别等问题。甚至在一些高可靠性高性能的应用环境中,还需要考虑使用怎样的外部存储系统(SAN、NAS或DAS)。下面将服务器的硬盘RAID卡的特点归纳一下:

如果是用作缓存服务器,比如Varnish或redis,则可以考虑用RAID 0。

如果是跑Nginx+FastCGI或Nginx等应用,则可以考虑用RAID 1。

如果是内网开发服务器或存放重要代码的服务器,则可以考虑用RAID 5。

如果是跑MySQL或Oracle等数据库应用,则可以考虑用固态硬盘做RAID 5或RAID 10。

5.网卡性能方面的考虑

如果基础架构是多服务器环境,而且服务器之间有大量的数据交换,那么建议为每台服务器配置两个或更多的网卡,一个用来对外提供服务,另一个用来做内部数据交换。由于现在项目外端都置于防火墙内,所以许多时候单网卡就足够了;而比如LVS+Keepalived这种只用公网地址的Linux集群架构,有时可能只需要一块网卡即可。建议大家选用万兆网卡。另外,建议交换机至少也要选择千兆网卡级别的。

如果采用的是AWS EC2云主机环境,单纯以EC2作为LVS或HAProxy意义不大。如果经常使用AWS EC2机器,应该会注意到AWS将机器的网卡性能分成了3个级别,即Low、Moderate、 High,那么这3个级别分别是什么情况呢?虽然AWS没有带宽限制,但是由于多虚拟机共享HOST物理机的网络性能和I/O性能,单个虚拟机的网络性能也不是特别好度量,不过大概情况是这样的:Low级别的是20M bit/s,Moderate级别的是40M bit/s,High级别的能达到80M bit/s~100M bit/s。从上面的分析情况可以得知,单台AWS EC2主机作为网站的负载均衡入口,容易成为网站的瓶颈。这个时候可以考虑使用AWS提供的Elastic Load Balancing产品,它可以在云中的多个Amazon EC2 实例间自动分配应用程序的访问流量(大家注意到没,相当于它将网站的流量分担到了多台机器上)。它可以实现更高水平的应用程序容错性能,从而无缝地提供分配应用程序流量所需的负载均衡容量。除了提供负载均衡常见的功能之外,它还具有Auto Scalling功能,关于Auto Scalling的详细介绍,可参见AWS官方文档。

6.服务器安全方面的考虑

由于目前国内的DDoS攻击还是比较普遍的,因此建议给每个项目方案和自己的电子商务网站配备硬件防火墙,比如Juniper、Cisco等硬件防火墙。当然了,这个问题也是网站后期运营维护需要考虑的,这里只是想让大家有个概念性的认识。此外,建议租赁CDN服务,这样万一不幸遭遇恶意的DDoS流量攻击,CDN还能帮助抵挡部分恶意流量,核心机房的业务不至于在很短的时间内就会崩溃。

7.根据机架数合理安排服务器的数量

这个问题应该在项目实施前就处理好了的,选择服务器时应该明确服务器的规格,即到底是1U、2U还是4U的,到底有多少台服务器和交换机,应该如何安排,毕竟机柜只有42U的容量。在小项目中这个问题可能无关紧要,但在大型项目的实施过程中,这个问题就很突出了。我们应该根据现有或额定的机架数目确定到底应该选择多少台服务器和交换机。

8.成本考虑:服务器的价格问题

无论是在公司采购时,还是在项目实施过程中,成本都是非常重要的问题。笔者的方案经常被退回,理由就是超出预算。尤其对于一些小项目,预算更吃紧。之前笔者经常面对的客户需求是为证券类资讯网站设计方案,只要求周一至周日的上午九点至下午三点网站不出问题即可,并不需要做复杂的负载均衡高可用。所以面对这种需求,笔者会将单Nginx或HAProxy设计成负载均衡,后面接两台Web应用服务器作为简单集群架构。如果是做中大型电子商务网站,那么在服务器成本上的控制就尤其重要了。事实上,我们经常面对的问题是,客户给出的成本预算有限,而实际应用又需要比较多的服务器,这时候,就不得不另外设计一套最小化成本预算方案来折中处理。

以上8个方面是我们在采购服务器时应该要注意的因素,在选择服务器的组件时要有所偏重,然后根据系统或网站架构来决定服务器的数量,尽量做到服务器资源利用的最大化。在控制方案成本的同时,要做到最优的性价比。

1.4 CentOS 6.4 x86_64最小化安装后的优化

购买了服务器以后要做的第一件事就是安装操作系统了,这里推荐安装CentOS 6.4 x86_64,安装系统时要选择最小化安装(不需要图形),在使用服务器时要记住一个原则,系统安装的应用程序包越少,服务器就会越稳定。至于服务器单机性能调优,应本着稳定安全的原则,尽量不要改动系统原有的配置(CentOS系统自身的文件和内存机制就很优秀),以下配置优化部分也适合Amazon Linux系统,大家可以对比参考。

1.4.1 系统的基础优化

1.更新yum官方源

CentOS 6.4 系统自带的更新源速度比较慢,想必各位都有所感受,国内的速度慢得让人受不了。为了让CentOS 6.4系统使用速度更快的yum更新源,一般做运维的都会选择更换源,笔者一般会选择网易的更新源,详细步骤如下所示。

1)下载repo文件,命令如下:

wget http://mirrors.163.com/.help/CentOS6-Base-163.repo

2)备份并替换系统的repo文件,命令如下:

cd /etc/yum.repos.d/

mv CentOS-Base.repo CentOS-Base.repo.bak

mv CentOS6-Base-163.repo CentOS-Base.repo

3)执行yum源更新,命令如下:

yum clean all #清除yum缓存

yum makecache #重建缓存

yum update #升级Linux系统

增加epel源,详细步骤如下所示。

1)下载rpm文件并进行安装,命令如下:

wget http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm

rpm -ivh epel-release-6-8.noarch.rpm

2)安装yum-priorities源优先级工具,命令如下:

yum install yum-priorities

yum-priorities源优先级工具是yum-plugin-priroites插件,用于给yum源划分优先级。比如说系统存在官方源、epel、puppetlabs3个yum源,这3个yum源中可能含有相同的软件,yum管理器会应用该工具来分辨安装软件时采用哪个yum源的软件。

如果说,设置官方的yum源优先级最高,epel yum源第二,puppetlabs第三(用1到99来表示,1最高),那么在安装程序的时候,就会先寻找官方的yum源。如果该源里面有所要的程序,那就停止寻找,直接安装找到的;如果没有找到,则依次寻找epel和puppetlabs的源。如果说3个yum源都含有同一个软件,那就安装优先级最高的官方yum源的。添加优先级的过程比较简单,只需要编辑对应的repo文件,在文件最后添加如下内容即可:

priority=对应优先级数字

注意,要想开启yum源的优先级功能,就要先确保priorities.conf文件里面有如下内容,需要先打开此文件,打开文件的命令如下:

vim /etc/yum/pluginconf.d/priorities.conf

确保文件里面包含如下内容:

[main]

enabled=1

2.关闭不需要的服务

众所周知,服务越少,系统占用的资源就会越少,所以应当关闭不需要的服务。建议把不需要的服务关闭掉,这样做的好处是减少内存和CPU资源占用。首先可以看下系统中存在着哪些已经启动了的服务,查看命令如下:

ntsysv

下面列出的是需要启动的服务,未列出的服务一律关闭。

crond:自动计划任务。

network:Linux系统的网络服务,很重要,若不开启此服务的话,服务器就不能联网。

sshd:OpenSSH服务器守护进程。

rsyslog:Linux的日志系统服务(CentOS 5.8下此服务名称为syslog),必须要启动。

3.关闭不需要的TTY

可用vim编辑器打开vim /etc/init/start-ttys.conf文件,文件内容如下所示:

start on stopped rc RUNLEVEL=[2345]

env ACTIVE_CONSOLES=/dev/tty[1-6]

env X_TTY=/dev/tty1

task

script

        . /etc/sysconfig/init

        for tty in $(echo $ACTIVE_CONSOLES) ; do

                [ "$RUNLEVEL" = "5" -a "$tty" = "$X_TTY" ] && continue

                initctl start tty TTY=$tty

        done

end script

这段代码使init打开了6个控制台,可分别用ALT+F1到ALT+F6进行访问。此6个控制台默认都驻留在内存中,用ps aux命令即可看到,命令如下:

ps aux | grep tty | grpe -v grep

命令显示结果如下所示:

root       1011  0.0  0.1   4060   288 tty1      Ss+  Nov25   0:00 /sbin/mingetty /dev/tty1

root      1013  0.0  0.1   4060   292 tty2     Ss+  Nov25   0:00 /sbin/mingetty /dev/tty2

root      1015  0.0  0.1   4060   292 tty3     Ss+  Nov25   0:00 /sbin/mingetty /dev/tty3

root      1017  0.0  0.1   4060   288 tty4     Ss+  Nov25   0:00 /sbin/mingetty /dev/tty4

root      1019  0.0  0.1   4060   288 tty5     Ss+  Nov25   0:00 /sbin/mingetty /dev/tty5

root      1021  0.0  0.1   4060   292 tty6     Ss+  Nov25   0:00 /sbin/mingetty /dev/tty6

root      7467  0.0  0.1   4072   340 hvc0     Ss+  Nov29   0:00 /sbin/agetty /dev/hvc0 

    38400 vt100-nav

事实上没有必要使用这么多,那如何关闭不需要的进程呢?

通常保留两个控制台就可以了,打开/etc/init/start-ttys.conf文件,注意以下代码内容:

env ACTIVE_CONSOLES=/dev/tty[1-6]

将[1-6]修改为[1-2],然后再打开/etc/sysconfig/init文件,注意以下代码内容:

ACTIVE_CONSOLES=/dev/tty[1-6]

将[1-6]修改为[1-2],然后重启机器即可。

4.对TCP/IP网络参数进行调整

调整TCP/IP网络参数,可以加强对抗SYN Flood的能力,命令如下:

echo 'net.ipv4.tcp_syncookies = 1' >> /etc/sysctl.conf

sysctl -p

5.修改SHELL命令的history记录个数

用vim编辑器打开/etc/profile文件,关注HISTSIZE=1000:

vi /etc/profile

在找到HISTSIZE=1000后,将其改为HISTSIZE=100(这条可根据实际工作环境而定)。

不需要重启系统也可让其生效,命令如下:

source /etc/profile

6.定时校正服务器的时间

我们可以定时校正服务器的时间,命令如下:

yum install ntp

crontab -e

加入一行:

*/5 * * * * /usr/sbin/ntpdate ntp.api.bz

ntp.api.bz 是一组NTP服务器集群,之前是6台服务器,位于上海电信;现在是3台服务器,分散于上海和浙江电信,可以用dig命令查看:

dig ntp.api.bz

命令显示结果如下所示:

; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.37.rc1.el6_7.5 <<>> ntp.api.bz

;; global options: +cmd

;; Got answer:

;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48560

;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:

;ntp.api.bz. IN A

;; ANSWER SECTION:

ntp.api.bz. 600 IN A 61.153.197.226

ntp.api.bz. 600 IN A 218.75.4.130

ntp.api.bz. 600 IN A 114.80.81.1

;; Query time: 10 msec

;; SERVER: 202.103.24.68#53(202.103.24.68)

;; WHEN: Thu Dec 24 06:58:36 2015

;; MSG SIZE  rcvd: 76

7.停止IPv6网络服务

在CentOS 6.4默认的状态下,IPv6是被启用的,可用如下命令查看:

lsmod | grep ipv6

命令显示结果如下:

nf_conntrack_ipv6       8748  2 

nf_defrag_ipv6         11182  1 nf_conntrack_ipv6

nf_conntrack           79357  2 nf_conntrack_ipv6,xt_state

ipv6                  321422  23 ip6t_REJECT,nf_conntrack_ipv6,nf_defrag_ipv6

有些网络和应用程序还不支持IPv6,因此,禁用IPv6可以说是一个非常好的选择,以加强系统的安全性,并提高系统的整体性能。不过,首先要确认一下IPv6是不是处于被启动的状态,命令如下:

ifconfig -a ← 列出全部网络接口信息

命令显示结果如下所示:

eth0      Link encap:Ethernet  HWaddr 00:16:3E:7F:67:C3  

          inet addr:192.168.1.207  Bcast:192.168.1.255  Mask:255.255.255.0

          inet6 addr: fe80::216:3eff:fe7f:67c3/64 Scope:Link

          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

          RX packets:405835 errors:0 dropped:0 overruns:0 frame:0

          TX packets:197486 errors:0 dropped:0 overruns:0 carrier:0

          collisions:0 txqueuelen:1000 

          RX bytes:327950786 (312.7 MiB)  TX bytes:17186162 (16.3 MiB)

          Interrupt:24 


lo        Link encap:Local Loopback  

          inet addr:127.0.0.1  Mask:255.0.0.0

          inet6 addr: ::1/128 Scope:Host

          UP LOOPBACK RUNNING  MTU:16436  Metric:1

          RX packets:0 errors:0 dropped:0 overruns:0 frame:0

          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

          collisions:0 txqueuelen:0 

          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)

然后修改相应的配置文件,停止IPv6,命令如下:

echo "install ipv6 /bin/true" > /etc/modprobe.d/disable-ipv6.conf

#每当系统需要加载IPv6模块时,强制执行/bin/true来代替实际加载的模块

echo "IPV6INIT=no" >> /etc/sysconfig/network-scripts/ifcfg-eth0

#禁用基于IPv6网络,使之不会被触发启动

8.调整Linux的最大文件打开数

要调整一下Linux的最大文件打开数,否则运行Squid服务的机器在高负载时执行性能将会很差;另外,在Linux下部署应用时,有时候会遇上“Too many open files”这样的问题,这个值也会影响服务器的最大并发数。其实Linux是有文件句柄限制的,但默认值不是很高,一般是1024,生产服务器很容易就会达到这个值,所以需要改动此值。

下面打开/etc/security/limit.conf命令,在最后一行添加如下命令:

* soft nofile 65535

* hard nofile 65535

正解的做法应该是除了以上步骤之外,还要在系统的/etc/rc.local文件里添加如下内容:

ulimit -SHn 65535

另外,ulimit -n命令并不能真正看到文件的最大文件打开数,可用如下脚本查看:

#!/bin/bash

for pid in `ps aux |grep nginx |grep -v grep|awk '{print $2}'`

do

cat /proc/${pid}/limits |grep 'Max open files'

done

在线上环境找一台cms业务机器,执行此脚本,显示结果如下所示:

Max open files            65535                65535                files     

Max open files            65535                65535                files     

Max open files            65535                65535                files     

Max open files            65535                65535                files     

Max open files            65535                65535                files     

Max open files            65535                65535                files     

Max open files            65535                65535                files     

Max open files            65535                65535                files     

Max open files            65535                65535                files     

Max open files            65535                65535                files  

9.启动网卡

在配置CentOS 6.4的网卡IP地址时,容易忽略的一项是Linux在启动时未启动网卡,其后果很明显,那就是该Linux机器永远也没有IP地址。下面是一台线上环境服务器网卡/etc/sysconfig/network-scripts/ifcfg-eth0文件的配置内容:

DEVICE=eth0

BOOTPROTO=static

HWADDR=00:14:22:1B:71:20

IPV6INIT=no

IPV6_AUTOCONF=yes

ONBOOT=yes   -->此项一定要记得更改为yes,它会在系统引导时就启动网卡设备

NETMASK=255.255.255.192

IPADDR=203.93.236.146

GATEWAY=203.93.236.129

TYPE=Ethernet 

PEERDNS=yes   -->允许用从DHCP处获得的DNS覆盖本地的DNS

USERCTL=no    -->不允许普通用户修改网卡

10.关闭写磁盘I/O功能

Linux文件默认有3个时间,分别如下所示。

atime:对此文件的访问时间。

ctime:此文件inode发生变化的时间。

mtime:此文件的修改时间。 

如果有多个小文件(比如Web服务器的页面上有多个小图片),通常是没有必要记录文件的访问时间的,这样就可以减少写磁盘的I/O,可这要如何配置呢?

首先,修改文件系统的配置文件/etc/fstab,然后,在包含大量小文件的分区中使用noatime和nodiratime这两个命令。例如:

/dev/sda5 /data/pics ext3 noatime,nodiratime 0 0

这样文件被访问时就不会再产生写磁盘的I/O了。

11.修改SSH登录配置

SSH服务配置优化,请保持机器中至少包含一个具有sudo权限的用户,下面的配置会禁止root远程登录,代码内容如下所示:

sed -i 's@#PermitRootLogin yes@PermitRootLogin no@' /etc/ssh/sshd_config #禁止root远程登录

sed -i 's@#PermitEmptyPasswords no@PermitEmptyPasswords no@' /etc/ssh/sshd_config #禁止空密码登录

sed -i 's@#UseDNS yes@UseDNS no@' /etc/ssh/sshd_config /etc/ssh/sshd_config #关闭 SSH反向查询,以加快SSH的访问速度

12.增加具有sudo权限的用户

添加用户的步骤和过程比较简单(这里略过),由于系统已经禁止了root远程登录,因此需要一个具有sudo权限的admin用户,权限跟root相当,这里用vim命令,在打开的/etc/sudoers文件内容里添加如下内容:

## Allow root to run any commands anywhere

root    ALL=(ALL)       ALL

然后添加如下内容:

admin   ALL=(ALL)       ALL

如果在进行sudo切换时不想输入密码,可以做如下更改:

admin  ALL=(ALL) NOPASSWD:ALL

1.4.2 优化Linux下的内核TCP参数以提高系统性能

内核的优化跟服务器的优化一样,应本着稳定安全的原则。下面以Squid服务器为例来说明,待客户端与服务器端建立TCP/IP连接后就会关闭Socket,服务器端连接的端口状态也就变为TIME_WAIT了。那是不是所有执行主动关闭的Socket都会进入TIME_WAIT状态呢?有没有什么情况可使主动关闭的Socket直接进入CLOSED状态呢?答案是主动关闭的一方在发送最后一个ACK后就会进入TIME_WAIT状态,并停留2MSL(报文最大生存)时间,这是TCP/IP必不可少的,也就是说这一点是“解决”不了的。

TCP/IP设计者如此设计,主要原因有两个:

防止上一次连接中的包迷路后重新出现,影响新的连接(经过2MSL时间后,上一次连接中所有重复的包都会消失)。

为了可靠地关闭TCP连接。主动关闭方发送的最后一个ACK(FIN)有可能会丢失,如果丢失,被动方会重新发送FIN,这时如果主动方处于 CLOSED状态,就会响应 RST而不是ACK。所以主动方要处于TIME_WAIT 状态,而不能是CLOSED状态。另外,TIME_WAIT 并不会占用很大的资源,除非受到攻击。

在Squid服务器中可输入如下命令查看当前连接统计数:

netstat -n | awk '/^tcp/ {++S[$NF]} END{for(a in S) print a, S[a]}'

命令显示结果如下所示:

LAST_ACK 14

SYN_RECV 348

ESTABLISHED 70

FIN_WAIT1 229

FIN_WAIT2 30

CLOSING 33

TIME_WAIT 18122

命令中的含义分别如下。

CLOSED:无活动的或正在进行的连接。

LISTEN:服务器正在等待进入呼叫。

SYN_RECV:一个连接请求已经到达,等待确认。

SYN_SENT:应用已经开始,打开一个连接。

ESTABLISHED:正常数据传输状态。

FIN_WAIT1:应用说它已经完成。

FIN_WAIT2:另一边已同意释放。

ITMED_WAIT:等待所有分组死掉。

CLOSING:两边尝试同时关闭。

TIME_WAIT:另一边已初始化一个释放。

LAST_ACK:等待所有分组死掉。

也就是说,这条命令可以把当前系统的网络连接状态分类汇总。

在Linux下高并发的Squid服务器中,TCP TIME_WAIT套接字的数量经常可达到两三万,服务器很容易就会被拖死。不过,可以通过修改Linux内核参数来减少Squid服务器的TIME_WAIT套接字数量,命令如下:

vim /etc/sysctl.conf 

然后,增加以下参数:

net.ipv4.tcp_fin_timeout = 30

net.ipv4.tcp_keepalive_time = 1200

net.ipv4.tcp_syncookies = 1

net.ipv4.tcp_tw_reuse = 1

net.ipv4.tcp_tw_recycle = 1

net.ipv4.ip_local_port_range = 10000 65000

net.ipv4.tcp_max_syn_backlog = 8192

net.ipv4.tcp_max_tw_buckets = 5000

以下将简单说明上面各个参数的含义:

net.ipv4.tcp_syncookies=1表示开启SYN Cookies。当出现SYN等待队列溢出时,启用Cookie来处理,可防范少量的SYN攻击。该参数默认为0,表示关闭。

net.ipv4.tcp_tw_reuse=1表示开启重用,即允许将TIME-WAIT套接字重新用于新的TCP连接。该参数默认为0,表示关闭。

net.ipv4.tcp_tw_recycle=1表示开启TCP连接中TIME-WAIT套接字的快速回收,该参数默认为0,表示关闭。

net.ipv4.tcp_fin_timeout=30表示如果套接字由本端要求关闭,那么这个参数将决定它保持在FIN-WAIT-2状态的时间。

net.ipv4.tcp_keepalive_time=1200表示当Keepalived启用时,TCP发送Keepalived消息的频度改为20分钟,默认值是2小时。

net.ipv4.ip_local_port_range=10 000 65 000表示CentOS系统向外连接的端口范围。其默认值很小,这里改为10 000到65 000。建议不要将这里的最低值设得太低,否则可能会占用正常的端口。

net.ipv4.tcp_max_syn_backlog=8192表示SYN队列的长度,默认值为1024,此处加大队列长度为8192,可以容纳更多等待连接的网络连接数。

net.ipv4.tcp_max_tw_buckets=5000表示系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数字,TIME_WAIT套接字将立刻被清除并打印警告信息,默认值为180 000,此处改为5000。对于Apache、Nginx等服务器,前面介绍的几个参数已经可以很好地减少TIME_WAIT套接字的数量,但是对于Squid来说,效果却不大,有了此参数就可以控制TIME_WAIT套接字的最大数量,避免Squid服务器被大量的TIME_WAIT套接字拖死。

执行以下命令使内核配置立马生效:

/sbin/sysctl –p

如果是用于Apache或Nginx等Web服务器,则只需要更改以下几项即可:

net.ipv4.tcp_syncookies=1

net.ipv4.tcp_tw_reuse=1

net.ipv4.tcp_tw_recycle = 1

net.ipv4.ip_local_port_range = 10000 65000

执行以下命令使内核配置立马生效:

/sbin/sysctl –p

如果是Postfix邮件服务器,则建议内核优化方案如下:

net.ipv4.tcp_fin_timeout = 30

net.ipv4.tcp_keepalive_time = 300

net.ipv4.tcp_tw_reuse = 1

net.ipv4.tcp_tw_recycle = 1

net.ipv4.ip_local_port_range = 10000 65000

kernel.shmmax = 134217728

执行以下命令使内核配置立马生效:

/sbin/sysctl –p

当然这些都只是最基本的更改,大家还可以根据自己的需求来更改内核的设置,比如我们的线上机器在高并发的情况下,经常会出现“TCP: too many orpharned sockets”的报错尽量也要本着服务器稳定的最高原则。如果服务器不稳定的话,一切工作和努力就都会白费。如果以上优化仍无法满足工作要求,则有可能需要定制你的服务器内核或升级服务器硬件。

1.4.3 CentOS 6.4 x86_64系统最小化优化脚本

CentOS 6.4 x86_64系统最小化优化脚本,脚本内容如下所示(请注意下面的代码中有中文注释内容,如果是放在线上运行时则要注意):

#!/bin/bash

#系统基础升级

wget http://mirrors.163.com/.help/CentOS6-Base-163.repo

cd /etc/yum.repos.d/

mv CentOS-Base.repo CentOS-Base.repo.bak

mv CentOS6-Base-163.repo CentOS-Base.repo

yum clean all #清除yum缓存

yum makecache #重建缓存

yum update  #升级Linux系统

#添加epel外部yum扩展源

cd /usr/local/src

wget http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm

rpm -ivh epel-release-6-8.noarch.rpm

#安装gcc基础库文件及sysstat工具

yum -y install gcc gcc-c++ vim-enhanced unzip unrar sysstat

#配置ntpdate自动对时

yum -y install ntp

echo "01 01 * * * /usr/sbin/ntpdate ntp.api.bz    >> /dev/null 2>&1" >> /etc/crontab

ntpdate ntp.api.bz

service crond restart

#配置文件的ulimit值

ulimit -SHn 65534

echo "ulimit -SHn 65534" >> /etc/rc.local

cat >> /etc/security/limits.conf << EOF

*                     soft     nofile             65534

*                     hard     nofile             65534

EOF


#基础系统内核优化

cat >> /etc/sysctl.conf << EOF

net.ipv4.tcp_syncookies = 1

net.ipv4.tcp_syn_retries = 1

net.ipv4.tcp_tw_recycle = 1

net.ipv4.tcp_tw_reuse = 1

net.ipv4.tcp_fin_timeout = 1

net.ipv4.tcp_keepalive_time = 1200

net.ipv4.ip_local_port_range = 10000 65535

net.ipv4.tcp_max_syn_backlog = 16384

net.ipv4.tcp_max_tw_buckets = 36000

net.ipv4.route.gc_timeout = 100

net.ipv4.tcp_syn_retries = 1

net.ipv4.tcp_synack_retries = 1

net.core.somaxconn = 16384

net.core.netdev_max_backlog = 16384

net.ipv4.tcp_max_orphans = 16384

EOF

/sbin/sysctl -p


#禁用control-alt-delete组合键以防止误操作

sed -i 's@ca::ctrlaltdel:/sbin/shutdown -t3 -r now@#ca::ctrlaltdel:/sbin/shutdown -t3 -r now@' /etc/inittab

#关闭SELinux

sed -i 's@SELINUX=enforcing@SELINUX=disabled@' /etc/selinux/config

#关闭iptables

service iptables stop

chkconfig iptables off

#ssh服务配置优化,请保持机器中至少存在一个具有sudo权限的用户,下面的配置会禁止root远程登录

sed -i 's@#PermitRootLogin yes@PermitRootLogin no@' /etc/ssh/sshd_config 

#禁止空密码登录

sed -i 's@#PermitEmptyPasswords no@PermitEmptyPasswords no@' /etc/ssh/sshd_config 

#禁止SSH反向解析

sed -i 's@#UseDNS yes@UseDNS no@' /etc/ssh/sshd_config /etc/ssh/sshd_config

service sshd restart

#禁用IPv6地址

echo "install ipv6 /bin/true" > /etc/modprobe.d/disable-ipv6.conf

#每当系统需要加载IPv6模块时,强制执行/bin/true来代替实际加载的模块

echo "IPV6INIT=no" >> /etc/sysconfig/network-scripts/ifcfg-eth0

#禁用基于IPv6网络,使之不会被触发启动

chkconfig ip6tables off

#vim基础语法优化

cat >> /root/.vimrc << EOF

set number

set ruler

set nohlsearch

set shiftwidth=2

set tabstop=4

set expandtab

set cindent

set autoindent

set mouse=v

syntax on

EOF

#精简开机自启动服务,安装最小化服务的机器初始可以只保留crond|network|rsyslog|sshd这4个服务

for i in `chkconfig --list|grep 3:on|awk '{print $1}'`;do chkconfig --level 3 $i off;done

for CURSRV  in crond rsyslog sshd network;do chkconfig --level 3 $CURSRV on;done

#重启服务器

reboot

1.4.4 Linux下CPU使用率与机器负载的关系与区别

笔者的线上竞标业务机器,在业务最繁忙的一段周期内,发现Nginx单机并发活动的连接数超过了2.6万,机器负载(基本上不到4,Nagios监控系统并没有发送报警邮件和短信)和Nginx+Lua服务都是正常的,网卡流量并没有打满,但流量就是怎么也打不进去。经过深入观察,发现这段时期内每台机器的CPU利用率都已经很高了,基本都维持在99%~100%左右,这种情况应该是CPU资源耗尽了,导致不能再继续提供服务,所以这里有必要研究下CPU负载和CPU利用率这两个概念的关系与区别。

CPU负载和CPU利用率虽然是两个不同的概念,但它们的信息可以显示在同一个top命令中。CPU利用率显示的是程序在运行期间实时占用的CPU百分比,而CPU负载显示的则是一段时间内正在使用和等待使用CPU的平均任务数。CPU利用率高,并不意味着负载就一定大。

网上有篇文章提供了一个有趣的比喻,即通过打电话来说明两者的区别,下面笔者按照自己的理解来阐述一下。

某公用电话亭,有一个人在打电话,四个人在等待,每人限定使用电话一分钟,若有人一分钟之内没有打完电话,则只能挂掉电话去排队,等待下一轮。电话在这里就相当于CPU,而正在或等待打电话的人就相当于任务数。

在电话亭的使用过程中,肯定会有人打完电话走掉,有人没有打完电话而选择重新排队,更会有新增的人在这儿排队,这个人数的变化就相当于任务数的增减。为了统计平均负载情况,我们5秒钟统计一次人数,并在第1、5、15分钟的时候对统计情况取平均值,从而形成第1、5、15分钟的平均负载。有的人拿起电话就打,一直打完一分钟;而有的人可能前三十秒在找电话号码,或者在犹豫要不要打,后三十秒才真正在打电话。如果把电话看作CPU,人数看作任务,就可以说前一个人(任务)的CPU利用率高,后一个人(任务)的CPU利用率低。

当然,CPU并不会在前三十秒工作,后三十秒歇着,只是说,有的程序涉及大量的计算,所以CPU利用率就高,而有的程序涉及计算的部分很少,CPU利用率自然就低。但无论CPU的利用率是高还是低,都跟后面有多少任务在排队没有必然关系。

CPU负载为多少才算是比较理想的呢?对于这个问题,业界一直存在争议,各有各的说法,个人比较赞同CPU负载小于等于0.5算是一种理想的状态。

不管某个CPU的性能有多好,1秒钟能处理多少个任务,我们都可以认为它无关紧要,虽然事实并非如此。在评估CPU负载时,我们只以5秒钟为单位来统计任务队列长度。如果每隔5秒钟统计的时候,发现任务队列的长度都是1,那么CPU负载就为1。假如只有一个单核的CPU,负载一直为1,那就意味着没有任务在排队,这说明目前机器系统负载还不错。还是以上面提到的竞标业务机器为例,都是四核机器,每个内核的负载为1的话,则总负载为4。也就是说,如果那些竞标服务器的CPU负载长期保持在4左右,还是可以接受的。

CPU使用率达到多少才算比较理想?

建议统计%user+%system的值(后面的章节有相关的Shell脚本),如果长期大于85%的话,就可以认为系统的CPU负载过重,这个时候就可以考虑添加物理CPU或增添业务集群机器了。

1.5 MySQL数据库的优化

网站上线初期,由于业务和名气的原因,访问量一般会处于一个比较低的水平。但随着业务量的扩大,网站宣传力度的增加,网站的PV和UV日渐增多,后端的MySQL数据库压力也越来越大,网站的查询功能或订单系统会越来越慢,那么客户的用户体验是相当糟糕的。那么究竟应该如何对MySQL数据库进行优化呢?下面就从MySQL服务器对硬件的选择、配置文件的优化等方面来说明这个问题。

1.5.1 服务器物理硬件的优化

在对MySQL服务器进行硬件挑选时,应该从下面几个方面着重对MySQL服务器的硬件配置进行优化,也就是说将项目中的资金着重投入到如下几处:

磁盘寻道能力(磁盘I/O)。笔者公司现在用的都是SAS15000转的硬盘,用6块这样的硬盘做RAID 10。MySQL数据库每一秒钟都在进行大量、复杂的查询操作,对磁盘的读写量可想而知,所以,通常认为磁盘I/O是制约MySQL性能的最大因素之一。对于日均访问量在1000万PV以上的Discuz论坛,如果磁盘I/O性能不好,造成的直接后果就是MySQL的性能会非常低下!解决这一制约因素可以考虑的解决方案是:使用RAID 10磁盘阵列,注意不要使用RAID 5磁盘阵列。MySQL在RAID5磁盘阵列上的效率不会像你期待的那样快,如果资金条件允许,可以选择固态硬盘SSD来代替SAS硬盘做RAID 10。

CPU对于MySQL的影响也是不容忽视的,建议选择运算能力强悍的CPU;推荐使用DELL PowerEdge R710,英特尔双至强E5504(四核心高性能CPU),该产品的卖点就是强大的虚拟化和数据处理能力。当然了,如果资金允许,可以考虑下更高级别的 DELL PowerEdge R910。

对于一台使用MySQL的数据库服务器而言,建议服务器的内存不要低于16GB,不过对于现在的服务器而言内存的大小是一个可以忽略的问题,如果是高端服务器,基本上内存都超过了32GB,笔者公司的数据库服务器都是32GB DDR3的内存。

强烈建议MySQL数据库服务器的系统为64操作系统(无论使用的是Windows系统还是Linux系统,如果没有特殊原因,建议MySQL数据库还是运行在64位的操作系统上),32位的系统存在非常多的制约。

1.5.2 利用tuning-primer脚本来调优MySQL数据库

MySQL在线上稳定运行一段时间后,就可以调用MySQL调优脚本tuning-primer.sh来检查参数的设置是否合理,该脚本的下载地址为:

http://www.day32.com/MySQL/tuning-primer.sh。 

该脚本使用“SHOW STATUS LIKE…”和“SHOW VARIABLES LIKE…”命令获得MySQL的相关变量和运行状态。然后根据推荐的调优参数对当前的MySQL数据库进行测试。最后根据不同颜色的标识来提醒用户需要注意的各个参数设置。

当前版本会处理如下这些推荐的参数:

Slow Query Log(慢查询日志)

Max Connections(最大连接数)

Worker Threads(工作线程)

Key Buffer(Key缓冲)

Query Cache(查询缓存)

Sort Buffer(排序缓存)

Joins(连接)

Temp Tables(临时表)

Table(Open & Definition)Cache(表缓存)

Table Locking(表锁定)

Table Scans(read_buffer)(表扫描,读缓冲)

InnoDB Status(InnoDB状态)

笔者之前所在公司的主营业务是CPA电子广告平台,公司规模比较小,所以没有配备专业的MySQL DBA,线上的MySQL数据库(四核CPU)服务器问题比较多,用tuning-primer.sh脚本扫描后发现有如下问题:

MySQL数据库有时连接非常慢,严重时会被拖死。

通过show full processlist命令可以发现大量的“unauthenticated user”连接,数据库肯定每次都要响应,所以速度越来越慢,解决方法其实很简单:在mysql.cnf里添加skip-name-resolve,即不启用DNS反向解析。

发生这种情况的原因其实也很简单,MySQL的认证实际上是user+host的形式(也就是说user可以相同),所以MySQL在处理新连接时会试着去解析客户端连接的IP,启用参数skip-name-resolve后MySQL授权的时候就只能使用纯IP的形式了。

数据库在繁忙期间负载很大,长期达到了13,远远超过了系统平均负载4,这个肯定是不正常的。

通过脚本扫描,发现没有新建thread_cache_size,所以加上了thread_cache_size=256,然后重启数据库,数据库的平均负载一下子降到了5~6。

发现数据库里有张new_cheat_id表,读取很频繁,而且长期处于Sending data状态。

怀疑是磁盘I/O压力过大所致,所以操作如下:

explain SELECT count(new_cheat_id)  FROM new_cheat WHERE account_id = '14348612' AND offer_id = '689'\G;

显示结果如下所示:

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: new_cheat

         type: ALL

possible_keys: NULL

          key: NULL

      key_len: NULL

          ref: NULL

         rows: 2529529

        Extra: Using where

1 row in set (0.00 sec)

上面出现的这种问题很严重,new_cheat没有建好索引,导致每次都要全表扫描2 529 529行记录,严重消耗了服务器的I/O资源,所以立即建好索引,并用show index命令查看了表索引:

show index from new_cheat;

命令显示结果如下所示:

+-----------+------------+------------+--------------+--------------+-

| Table     | Non_unique | Key_name   | Seq_in_index | Column_name  | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |

+-----------+------------+------------+--------------+--------------+-

| new_cheat |          0 | PRIMARY    |            1 | new_cheat_id | A         |     2577704 |     NULL | NULL   |      | BTREE      |         | 

| new_cheat |          1 | ip         |            1 | ip           | A         |     1288852 |     NULL | NULL   |      | BTREE      |         | 

| new_cheat |          1 | account_id |            1 | account_id   | A         |     1288852 |     NULL | NULL   |      | BTREE      |         | 

+-----------+------------+------------+--------------+--------------+-

3 rows in set (0.01 sec)

再来查看explain结果:

explain SELECT count(new_cheat_id)  FROM new_cheat WHERE account_id = '14348612' AND offer_id = '689'\G;

显示结果如下所示:

*************************** 1. row ***************************

           id: 1

  select_type: SIMPLE

        table: new_cheat

         type: ref

possible_keys: account_id

          key: account_id

      key_len: 4

          ref: const

         rows: 6

        Extra: Using where

1 row in set (0.00 sec)

大家可以发现,加好了索引后,此SQL通过account_id索引直接读取了6条记录(请对比关注rows这行)就获得了查询结果,系统负载由5~6直接降到了3.07~3.66了,这个负载还是能在可接受范围之内的。

MySQL的explain命令可用于SQL语句的查询执行计划(QEP)。这条命令的输出结果能够让我们了解MySQL 优化器是如何执行SQL 语句的。这条命令并没有提供任何调整建议,但它提供的重要信息能够帮助你做出调优决策。

最后要说明一点的是,对于网站来说,MySQL单机优化对整体性能提升的作用毕竟有限,尤其是在MySQL单机写入方面,如果在工作中遇到了那种对MySQL即时写入和读取速度要求很高的场景,建议大家可以多关注分布式的SQL解决方案,例如Hadoop的HBase和AWS的RedShift等分布式SQL系统。

1.6 小结

本章介绍了系统架构设计的相关专业术语,以及关于IDC机房选择、物理服务器和AWC EC2类型实例的选择,最后介绍了CentOS 6.4 x86_64系统的优化及MySQL数据库的简单调优,这些都是系统架构设计的基础,希望大家能够掌握此章的内容,这对于我们以后的工作会有很大的帮助。







第2章

生产环境下的Shell和Python脚本

接触Linux系统十多年了,Shell和Python脚本都已经完全融入笔者的生活中了。虽然Shell脚本只是一个简单的解释型语言,不受开发人员的重视,但对于系统运维工程师来说,它的作用举足轻重,它就像我们的瑞士军刀一样,可以帮助我们简化日常的工作并减少工作量。在系统维护工作中,Shell脚本常常能比用C或C++语言编写的程序更快地解决相同的问题,此外,Shell脚本具有很好的可移植性,有时跨越Unix与POSIX兼容的系统,只须略作修改即可,甚至不必修改即可使用Shell脚本。不过,Shell脚本毕竟是一门系统脚本语言,所以很多高级语言的特性它都不具备,比如面向对象和丰富的第三方类库支持,不过这一点我们可以用Python脚本来弥补。

2.1 Shell和Python语言的简单介绍

Shell是大家熟悉的一种脚本语言,这里简单介绍下它在日常工作中的应用。

1)配合Crontab帮助我们定时执行任务,就像MS的计划任务一样。很多朋友向笔者反映说Crontab不能做秒级的计划任务,其实只要写一个Shell脚本,用while..do..done循环后放入后台执行就可以实现秒级的计划任务了。不过要记得避免出现死循环的问题。

2)开发Nagios监控插件,比如我们的线上的许多业务需求都是通过Shell语言开发的。

3)配合iptables可形成方便又安全的iptables脚本,还可保护主机的安全。

4)文本过滤筛选和大数据日志分析,笔者公司的许多离线数据都是通过Shell配合Python进行分析处理的。

5)可以写强大的系统性能和状态监控脚本,并配合Keepalived来实现系统的高可用。

6)定时备份任务和rsync同步重要的服务器资料,这是Shell的基本功能。

7)自动化安装系统环境,规范化操作,缩减项目实施的时间和误差。

Shell的强大和其他未挖掘出的功能需要我们在日常工作中不断地发现和总结,不过,在感叹Shell脚本管理功能强大的同时,也应该清楚Shell脚本在开发功能上的不足之处,所以这里向大家推荐Python,它继承了传统编译语言的强大性和通用性,同时也借鉴了简单脚本和解释语言的易用性,运行速度也不慢,适合于网站开发,正好可以弥补Shell脚本的不足。

Python是一种动态解释型的编程语言。Python功能强大,可支持面向对象、函数式编程,同时还可以在Windows、Linux和Unix等多个操作系统上使用,因此被称为“胶水语言”。Python的简洁性、易用性使得开发过程变得简练,特别适用于快速应用开发。笔者也发现,Python代码在其所在公司的系统中无处不在,在线上Github版本管理库中的比重也长期占据第一、第二的位置,Python的具体应用和流行原因在后续章节里会详细说明。

2.2 Shell编程基础

Shell是核心程序Kernel之外的命令解析器,是一个程序,同时也是一种命令语言和程序设计语言。

作为一种命令语言Shell可以交互式地解析用户输入的命令。

作为一种程序设计语言Shell定义了各种参数,并且提供了高级语言才有的程序控制结构,虽然它不是Linux核心系统的一部分,但是它调用了Linux核心的大部分功能来执行程序,建立文件并以并行的方式来协调程序的运行。

比如,输入命令ls后,Shell就会解析ls这个命令并且向内核发出请求,内核执行这个命令之后,把结果告诉Shell,然后Shell把结果输出到屏幕。

Shell相当于是Windows系统下的command.com,在Windows中这样的解析器只有一个,但是在Linux中这样的解析器有很多个,比如Sh、Bash和Ksh等。

可通过echo $SHELL来查看自己运行的Shell。在Shell中还可以运行子Shell,直接输入csh命令就可以进入csh界面了。

Linux默认的Shell是Bash,下面的内容主要以此为主。

2.2.1 Shell脚本的基本元素

Shell脚本的第一行通常为如下内容:

#!/bin/bash  //第一行

#          //表示单行注释

如果是多行注释呢,应该如何操作?多行注释如下所示:

:<<BLOCK

中间部分为要省略的内容

BLOCK

Shell脚本的第一行均包含一个以#!为起始标志的文本行,这个特殊的起始标志表示当前文件包含一组命令,需要提交给指定的Shell解释执行。紧随#!标志的是一个路径名,指向执行当前Shell脚本文件的命令解释程序。比如:

#!/bin/bash

再比如:

#!/usr/bin/ruby

如果Shell脚本中包含多个特殊的标志行,那么只有一个标志行会起作用。

2.2.2 Shell特殊字符

下面来看看Shell特殊字符。

双引号":用来使Shell无法认出除字符$、`、\之外的任何字符或字符串,也称之为弱引用。

单引号':用来使Shell无法认出所有的特殊字符,也称之为强引用。

反引号`:优先执行当前命令。

反斜杠\:有两种作用,一种是用来使Shell无法认出其后的字符,使其后的字符失去特殊的含义,如有特殊含义的字符$,也称为转义符。另外,如果放在指令前,有取消别名的作用,例如在“\rm/home/yhc/*.log”中,rm指令前加上\,作用是暂时取消别名的功能,将rm指令还原。

分号; :允许在一行上放多个命令。

&     :将命令放于后台执行,建议带上nohup。

括号() :创建成组的命令。

大括号{}:创建命令块。

<>&    :重定向。

*? [] !:表示模式匹配。

$      :变量名的开头。

#      :表示注释(第一行除外)。

空格、制表符、换行符:当作空白。

2.2.3 变量和运算符

变量是放置在内存中的某个存储单元,这个存储单元里存放的是这个单元的值,这个值是可以改变的,我们称之为变量。

其中,本地变量是在用户现有的Shell生命周期的脚本中使用的,用户退出后变量就不存在了,该变量只用于该用户。

下面都是跟变量相关的命令,这里只是大致地说明下,后面的内容里会有详细的说明,如下所示:

变量名="变量"

readonly 变量名="变量"表示设置该变量为只读变量,这个变量不能被改变。

echo $变量名

set  显示本地所有的变量

unset 变量名 表示清除变量

readonly 显示当前Shell下有哪些只读变量

环境变量用于所有用户进程(包括子进程)。Shell中执行的用户进程均称为子进程。不像本地变量只用于现在的Shell。环境变量可用于所有的子进程,它包括编辑器、脚本和应用。

环境变量主目录如下:

$HOME/.bash_profile(/etc/profile)

设置环境变量,例句如下:

export test="123"

查看环境变量,命令如下:

env

或者用如下命令:

export

本地变量中包含环境变量。环境变量既可以运行于父进程,也可以运行于子进程中。本地变量则不能运行于所有的子进程中。

变量清除命令如下:

unset 变量名

再来看看位置变量,在运行某些程序时,程序中会带上一系列参数,若我们要用到这些参数,则会采用位置来表示,对于这样的变量,我们称之为位置变量,目前在Shell中的位置变量有10个($0~$9),超过10个则用其他方式表示,其中,$0表示整个Shell脚本,这点要记住。

下面举例说明位置变量的用法。比如,有如下test.sh脚本内容:

#!/bin/bash

echo "第一个参数为": $0"

echo "第二个参数为": $1"

echo "第三个参数为": $2"

echo "第四个参数为": $3"

echo "第五个参数为": $4"

echo "第六个参数为": $5"

echo "第七个参数为": $6"

现在给予test.sh执行权限,命令如下:

chmod +x test.sh

./test.sh  1 2 3 4 5 6

命令结果显示如下:

第一个参数为: ./test.sh

第二个参数为: 1

第三个参数为: 2

第四个参数为: 3

第五个参数为: 4

第六个参数为: 5

第七个参数为: 6

值得注意的是,从第10个位置参数开始,必须使用花括号括起来,如:${10}。特殊变量$*和$@表示所有的位置参数。特殊变量$#表示位置参数的总数。

下面我们进一步详细说明下Shell的知识要点。

1.运行Shell脚本

Shell脚本有两种运行方式,第一种方式是利用sh命令,把Shell脚本文件名作为参数。这种执行方式要求Shell脚本文件具有“可读”的访问权限,然后输入sh test.sh即可执行。

第二种执行方式是利用chmod命令设置Shell脚本文件,使Shell脚本具有“可执行”的访问权限。然后直接在命令提示符下输入Shell脚本文件名,例如./test.sh。

2.调试Shell脚本

使用bash -x 可以调试Shell脚本,bash会先打印出每行脚本,再打印出每行脚本的执行结果,如果只想调试其中几行脚本,可以用set -x和set +x把要调试的部分包含进来,命令如下:

set -x

脚本部分内容

set +x

这个时候可以直接运行脚本,而不需要再执行 bash -x 了。这个功能在实际工作中非常有用,可以帮助我们调试变量,找出bug点,总之是非常有用的功能,希望大家掌握。

3.退出或出口状态

一个Unix进程或命令运行终止时,将会自动地向父进程返回一个出口状态。如果进程成功执行完毕,将会返回一个数值为0的出口状态。如果进程在执行过程中出现异常而未能正常结束时,将会返回一个非零值的出错代码。

在Shell脚本中,可以利用“exit[n]”命令在终止执行Shell脚本的同时,向调用脚本的父进程返回一个数值为n的Shell脚本出口状态。其中,n必须是一个位于0~255范围内的整数值。如果Shell脚本是以不带参数的exit语句结束执行的,则Shell脚本的出口状态就是脚本中最后执行的那条命令的出口状态。

在Unix系统中,为了测试一个命令或Shell脚本的执行结果,$?内部变量将返回之前执行的最后一条命令的出口状态,这些状态中,0才是正确值,其他非零的值都表示是错误的。

4. Shell变量

Shell变量名可以由字母、数字和下划线等字符组成,但第一个字符必须是字母或下划线。

Shell中的所有变量都是字符串类型的,它并不区分变量的类型,如果变量中包含下划线(_)的话,就要注意了,有些脚本的区别就很大,比如脚本中$PROJECT_svn_$DATE.tar.gz与${PROJECT}_svn_${DATE}.tar.gz的区别就很大,注意变量${PROJECT_svn},如果不用{}将变量全部包括的话,Shell则会理解成变量$PROJECT,后面再接着_svn。

从用途上考虑,变量可以分为内部变量、本地变量、环境变量、参数变量和用户自定义的变量,下面分别说明它们各自的定义

内部变量是为了便于Shell编程而由Shell设定的变量。如错误类型的ERRNO变量。

本地变量是在代码块或函数中定义的变量,且仅在定义的范围内有效。

参数变量是调用Shell脚本或函数时传递的变量。

环境变量是为系统内核、系统命令和用户命令提供运行环境而设定的变量。

用户自定义的变量是为运行用户程序或完成某种特定的任务而设定的普通变量或临时变量。

5.变量的赋值

变量的赋值可以采用赋值运算符=来实现,其语法格式如下:

variable=value

注意,赋值运算符前后不能有空格,否则会报错,写惯了Python后再回头写Shell脚本就会经常犯这种错误;未初始化的变量值为null,使用如下变量赋值的形式,即可声明一个未初始化的变量。

如果variable=value赋值运算符前后有空格,则会出现如下报错信息:

err = 72

-bash: err: command not found

写惯了Python程序后再回头写Shell脚本,笔者也经常也会犯这种错误,大家不要忘了,Shell的语法其实也是很严谨的。

6.内部变量

Shell提供了丰富的内部变量,为用户的Shell编程提供各种支持。内部变量及其意义如下所示。

PWD:表示当前的工作目录,其变量值等同于PWD内部命令的输出。

RANDOM:每次引用这个变量时,将会生成一个均匀分布的0~32 767范围内的随机整数。

SCONDS:脚本已经运行的时间(秒)。

PPID:当前进程的父进程的进程ID。

$?:表示最近一次执行的命令或Shell脚本的出口状态。

7.环境变量

主要环境变量及其意义如下所示。

EDITOR:用于确定命令行编辑所用的编辑程序,通常为vim。

HOME:用户主目录。

PATH:指定命令的检索路径。

例如,要将/usr/local/mysql/bin目录添加到系统默读的PATH变量中,应该如何操作呢?

PATH=$PATH:/usr/local/mysql/bin

export PATH

echo $PATH

如果想让其重启或重开一个Shell也生效,又该如何操作呢?

Linux中包含了两个重要的文件: /etc/profile和$HOME/.bash_profile,每次系统登录时都要读取这两个文件,用来初始化系统所用到的变量,其中/etc/profile是超级用户所用的,$HOME/.bash_profile是每个用户自己独立的,可以通过修改该文件来设置PATH变量。

这种方法只能使当前用户生效,并非所有用户。

如果要让所有用户都能够用到此PATH变量,可以用vim命令打开/etc/profile文件,并在适当位置添加PATH=$PATH:/usr/local/mysql/bin,然后执行source /etc/profile使其生效。

8.变量的引用和替换

假定variable是一个变量,在变量名字前加上“$”前缀符号,即可引用变量的值,表示使用变量中存储的值来替换变量名字本身。

引用变量有两种形式:$variable与${variable}。

位于双引号中的变量可以进行替换,但位于单引号中的变量则不能进行替换。

9.变量的间接引用

假定一个变量的值是另一个变量的名字,那么根据第一个变量可以获得第三个变量的值。举例说明如下:

a=123

b=a

eval c=\${$b}

echo $b

echo $c

实际工作中不推荐使用这种用法,因为写出来的脚本容易产生歧义,让人混淆,而且也不方便在团队里面交流工作。

10.变量声明与类型定义

尽管Shell并未严格地区分变量的类型,但在Bash中,可以使用typeset或declare命令定义变量的类型,并在定义时进行初始化。

11.部分常用命令介绍

这里将介绍工作中常用的部分Shell命令,如下所示。

(1)冒号

冒号(:)与true语句不执行任何实际的处理动作,但可用于返回一个出口状态为0的测试条件。这两个语句常用于while循环结构的无限循环测试条件,在脚本中经常会见到这样的用法:

while :

这表示是一个无限循环的过程,所以使用的时候要特别注意,不要成了死循环,所以一般会定义一个sleep时间,可以实现秒级别的cron任务,其语法格式如下:

while :

do 

命令语句

sleep 自己定义的秒数

done

(2)echo与print命令

print的功能与echo的功能完全一样,主要用于显示各种信息。

(3)read命令

read语句的主要功能是读取标准输入的数据,然后存储到变量参数中。如果read命令的后面有多个变量参数,则输入的数据会按空格分隔的单词顺序依次为每个变量赋值。read在交互式脚本中相当有用,建议大家掌握。

read命令用于接收标准输入设备(键盘)的输入,或其他文件描述符的输入(后文再详细说明)。得到输入后,read命令将数据放入一个标准变量中。下面是read命令的最简单形式:

#!/bin/bash

echo -n "Enter your name:" #参数-n的作用是不换行,echo默认是换行

read  name                   #从键盘输入 

echo "hello $name,welcome to my program"   #显示信息

exit 0                       #退出Shell程序。

由于read命令提供了-p参数,允许在read命令行中直接指定一个提示,因此上面的脚本可以简写成下面的脚本:

#!/bin/bash

read -p "Enter your name:" name

echo "hello $name, welcome to my program"

exit 0

(4)set命令

set命令用于修改或重新设置位置参数的值。Shell规定,用户不能直接为位置参数赋值。使用不带参数的set将会输出所有的内部变量。

“set --”用于清除所有的位置参数。

(5)unset命令

该命令用于清除Shell变量,把变量的值设置为null。这个命令并不影响位置参数。

(6)expr命令

expr命令是一个手工命令行计数器,用于在Linux下求表达式变量的值,一般用于整数值,也可用于字符串。其格式为:

expr Expression

expr命令读入Expression参数,计算它的值,然后将结果写入到标准输出

Expression参数应用规则如下:

用空格隔开每个项。

用/(反斜杠)放在Shell的特定字符前面。

对于包含空格和其他特殊字符的字符串要用引号括起来。

      expr命令支持的整数算术运算表达式如下:

exp1+exp2,计算表达式exp1和exp2的和。

exp1-exp2,计算表达式exp1和exp2的差。

exp1/*exp2,计算表达式exp1和exp2的乘积。

exp1/exp2,计算表达式exp1和exp2的商。

exp1%exp2,计算表达式exp1与exp2的余数。

另外expr命令还支持字符串比较表达式,语句如下:

str1=str2

该语句是比较字符串str1和str2是否相等,如果计算结果为真,则同时输出1,返回值为0;反之计算结果为假,则同时输出0,返回值为1。

要说明的是,expr默认是不支持浮点运算的,比如我们想在expr下面输出echo "1.2*7.8"的运算结果,那是不可能的,那么应该怎么办呢?这里可以用到bc,举例说明如下:

echo "scale=2;1.2*7.8" |bc

#这里的scale用来控制小数点精度,默认为1

(7)let命令

let命令取代并扩展了expr命令的整数算术运算。let命令除了支持expr支持的5种算术运算外,还支持+=、-=、*=、/=、%=。

12.数值常数

Shell脚本按十进制解释字符串中的数字字符,除非数字前有特殊的前缀或记号,若数字前有一个0则表示为一个八进制的数,0x或0X则表示为一个十六进制的数。

13.命令替换

命令替换的目的是获取命令的输出,且为变量赋值或对命令的输出做进一步的处理。命令替换实现的方法为采用$(...)的形式引用命令或使用反向引号引用命令'command'。如:

today=$(date)

echo $today

如果文件filename中包含需要删除的文件列表时,则采用如下命令:

rm $(cat filename)

14. test语句

test语句与if/then和case结构的语句一起,构成了Shell编程的控制转移结构。

test命令的主要功能是计算紧随其后的表达式,检查文件的属性、比较字符串或比较字符串内含的整数值,然后以表达式的计算结果作为test命令的出口状态。如果test命令的出口状态为真,则返回0;如果为假,则返回一个非0的数值。

test命令的语法格式有:test expression或[ expression ],注意方括号内侧的两边必须各有一个空格。

[[ expression ]]是一种比[ expression ]更通用的测试结构,也可用于扩展test命令。

15.文件测试运算符

文件测试主要指文件的状态和属性测试,其中包括文件是否存在、文件的类型、文件的访问权限及其他属性等。

下面各项为文件属性测试表达式。

-e file,如果给定的文件存在,则条件测试的结果为真。

-r file,如果给定的文件存在,且其访问权限是当前用户可读的,则条件测试的结果为真。

-w file,如果给定的文件存在,且其访问权限是当前用户可写的,则条件测试的结果为真。

-x file,如果给定的文件存在,且其访问权限是当前用户可执行的,则条件测试的结果为真。

-s file,如果给定的文件存在,且其大于0,则条件测试的结果为真。

-f file,如果给定的文件存在,且是一个普通文件,则条件测试的结果为真。

-d file,如果给定的文件存在,且是一个目录,则条件测试的结果为真。

-L file,如果给定的文件存在,且是一个符号链接文件,则条件测试的结果为真。

-c file,如果给定的文件存在,且是字符特殊文件,则条件测试的结果为真。

-b file,如果给定的文件存在,且是块特殊文件,则条件测试的结果为真。

-p file,如果给定的文件存在,且是命名的管道文件,则条件测试的结果为真。

常见代码举例如下: 

BACKDIR=/data/backup

[ -d ${BACKDIR} ] || mkdir -p ${BACKDIR}

[ -d ${BACKDIR}/${DATE} ] || mkdir ${BACKDIR}/${DATE}

[ ! -d ${BACKDIR}/${OLDDATE} ] || rm -rf ${BACKDIR}/${OLDDATE}

下面是字符串测试运算符。

-z str,如果给定的字符串的长度为0,则条件测试的结果为真。

-n str,如果给定的字符串的长度大于0,则条件测试的结果为真。要求字符串必须加引号。

s1=s2,如果给定的字符串s1等同于字符串s2,则条件测试的结果为真。

s1!=s2,如果给定的字符串s1不等同于字符串s2,则条件测试的结果为真。

s1<s2,如果给定的字符串s1小于字符串s2,则条件测试的结果为真。

s1>s2,若给定的字符串s1大于字符串s2,则条件测试的结果为真。

在比较字符串的test语句中,变量或字符串表达式的前后一定要加双引号。

再来看看整数值测试运算符。test语句中整数值的比较会自动采用C语言中的atoi()函数把字符转换成等价的ASC整数值。所以可以使用数字字符串和整数值进行比较。整数测试表达式为:-eq(等于)、-ne(不等于)、-gt(大于)、-lt(小于)、-ge(大于等于)、-le(小于等于)。

16.逻辑运算符

Shell中的逻辑运算符及其意义如下所示:

(expression):用于计算括号中的组合表达式,如果整个表达式的计算结果为真,则测试结果也为真。

!exp:可对表达式进行逻辑非运算,即对测试结果求反。例如:test ! -f file1。

符号-a或&&:表示逻辑与运算。

符号-o或||:表示逻辑或运算。

Shell脚本中的用法可参考图2-1。


图2-1 &&与||指令说明 

17. Shell中的自定义函数

自定义函数语法比较简单,如下:

function 函数名()

{

    action;

    [return 数值;]

}

具体说明如下:

自定义函数既可以用带function参数的函数名()定义,也可以直接用函数名()定义,而不用带任何参数。

参数返回时,可以显式地加return返回,如果不加,则将以最后一条命令的运行结果作为返回值。return后跟数值,取值范围0~255。

举例说明,遍历/usr/local/src目录里面包含的所有文件(包括子目录),脚本内容如下:

#!/bin/bash  

function traverse(){

for file in 'ls $1'

      do

         if [ -d $1"/"$file ]

         then

            traverse $1"/"$file

         else

            echo $1"/"$file 

         fi

      done

   } 

traverse "/usr/local/src"

18. Shell中的数组

Shell是支持数组的,但仅支持一维数组(不支持多维数组),并且没有限定数组的大小。类似于C语言,数组元素的下标由0开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于0。

(1)定义数组

在Shell中,用括号来表示数组,数组元素之间用空格符号分隔开。定义数组的一般形式为:

array_name=(value1 ... valuen)

例如:

array_name=(value0 value1 value2 value3)

或者:

array_name=(

value0

value1

value2

value3

)

还可以单独定义数组的各个分量:

array_name[0]=value0

array_name[1]=value1

array_name[2]=value2

可以不使用连续的下标,而且下标的范围没有限制。

(2)读取数组

读取数组元素值的一般格式为:

${array_name[index]}

例如:

valuen=${array_name[2]}

下面用一个Shell脚本举例说明上面的用法,脚本内容如下所示:

#!/bin/bash

NAME[0]="yhc"

NAME[1]="cc"

NAME[2]="gl"

NAME[3]="wendy"

echo "First Index: ${NAME[0]}"

echo "Second Index: ${NAME[1]}"

运行脚本,命令如下所示:

bash ./test.sh

输出结果如下所示:

First Index: Zara

Second Index: Qadir

使用@或*可以获取数组中的所有元素,例如:

${array_name[*]}

${array_name[@]}

对上面的代码加上最后两行,如下所示:

echo "${NAME[*]}"

echo "${NAME[@]}"

运行脚本,输出:

First Index: yhc

Second Index: cc

yhc cc gl wendy

yhc cc gl wendy

(3)获取数组的长度

获取数组长度的方法与获取字符串长度的方法相同,例如:

获取数组元素的个数,命令如下所示:

length=${#array_name[@]}

获取数组单个元素的长度,命令如下所示:

length=${#array_name[*]}

19. Shell中的字符串截取

Shell截取字符串的方法有很多,一般常用的有以下几种方法。

先来看第一种方法,从不同的方向截取。

从左向右截取最后一个string后的字符串,命令如下:

${varible##*string}

从左向右截取第一个string后的字符串,命令如下:

${varible#*string}

从右向左截取最后一个string后的字符串,命令如下:

${varible%%string*}

从右向左截取第一个string后的字符串,命令如下:

${varible%string*}

下面是第二种方法。

${变量:n1:n2}:截取变量从n1开始的n2个字符,组成一个子字符串。可以根据特定字符偏移和长度,使用另一种形式的变量扩展方式来选择特定的子字符串,例如下面的命令:

${2:0:4}

这种形式的字符串截断非常简便,只须用冒号分开来指定起始字符和子字符串的长度即可,工作中用得最多的也是这种方式。

还有第三种方法。

这里利用cut命令来获取后缀名,命令如下:

ls -al | cut -d "." -f2

2.3 Shell中的控制流结构

Shell中的控制流结构也比较清晰,如下所示:

if ...then... else...fi语句

case语句

for循环

until循环

while循环

break控制

continue控制

工作中用得最多的就是if语句、for循环、while循环及case语句,大家可以以这几个为重点对象来学习。

if语句语法如下:

if 条件1

then

  命令1

else

  命令2

fi

if语句的进阶用法:

if 条件1

then

    命令1

      else if 条件2

    then

        命令2

else

    命令3

fi

举例说明下if语句的用法,示例如下:

#!/bin/bash

if [ "10" -lt "12" ]

then

        echo "10确实比12小"

else 

        echo  "10不小于12"

fi

case语句语法如下:

case 值 in

模式1)

    命令1

    ;;

模式2)

    命令2

    ;;

*)

    命令3

    ;;

esac

case取值后面必须为单词in,每一模式必须以右括号结束。取值可以为变量或常数。匹配发现取值符合某一模式后,其间所有的命令都开始执行直至“;;”。模式匹配符“*”表示任意字符,“?”表示任意单字符,“[..]”表示类或范围中任意字符。

case语句适合打印成绩或用于/etc/init.d/服务类脚本。以下面的脚本为例来说明下。

#!/bin/bash

#case select

echo -n "Enter a number from 1 to 3:"

read ANS

case $ANS in

1)

        echo "you select 1"

        ;;

2)

        echo "you select 2"

        ;;

3)

        echo "you select 3"

        ;;

*)

        echo "`basename $0`: this is not between 1 and 3"

        exit;

        ;;

esac

下面是稍为复杂的实例说明,/etc/init.d/syslog脚本的部分代码如下,大家注意case语句的用法,可以以此为参考编写自己的case脚本:

case "$1" in  

    start)  

        start  

        exit 0  

    ;;  

    stop)  

        stop  

        exit 0  

    ;;  

    reload|restart|force-reload)  

        stop  

        start  

        exit 0  

    ;;  

    **)  

        echo "Usage: $0 {start|stop|reload|restart|force-reload}"

        exit 1  

    ;;  

esac  

for循环语句的语法如下:

for 变量名 in 列表

do

    命令

done

若变量值在列表里,则for循环执行一次所有命令,使用变量名访问列表并且取值。命令可为任何有效的Shell命令和语句,变量名为任意单词。in列表可以包含列表、字符串和文件名,还可以是数值范围,例如{100..200},举例说明如下:

#!/bin/bash

for n in {100..200}

do

         host=192.168.1.$n

         ping -c2 $host &>/dev/null

         if [ $? = 0 ]; then

                      echo "$host is UP"

         else 

                      echo "$host is DOWN"

         fi

done

while循环语句的语法如下:

while条件

do

    命令

done

在Linux中有很多逐行读取一个文件的方法,其中最常用的就是下面脚本里的方法(管道法),而且这也是效率最高、使用最多的方法,笔者最喜欢用的也是管道法。为了给大家一个直观的感受,下面将通过生成一个大文件的方式来检验各种方法的执行效率。

在脚本里,LINE这个变量是预定义的,并不需要重新定义,$FILENAME后面接系统中实际存在着的文件名。 

管道方法的命令语句如下:

cat $FILENAME | while read LINE

脚本举例说明如下:

#!/bin/bash

cat test.txt | while read LINE

do

    echo $LINE

    done

}

2.4 sed的基础用法及实用示例

sed是Linux平台下的轻量级流编辑器,一般用于处理文本文件。sed有许多很好的特性。首先,它相当小巧;其次,它可以配合强大的Shell来完成很多复杂的功能。在笔者看来,完全可以把sed当作一个脚本解释器,用类似于编程的手段来完成很多事情。我们完全可以用sed的方式来处理日常工作中的大多数文档。它跟vim最大的区别在于:sed不需要像vim一样打开文件,而是可以在脚本里面直接操作文档,所以大家将会发现它在Shell脚本里的使用频率是很高的。

2.4.1 sed的基础语法格式

sed的语法格式如下所示: 

sed [-nefr] [n1,n2] 动作

其中:

-n是安静模式,只有经过sed处理过的行才会显示出来,其他不显示。

-e表示直接在命令行模式上进行sed的操作。貌似是默认选项,不用写。

-f将sed的操作写在一个文件里,用的时候 -f filename 就可以按照内容进行sed操作了。

-r表示使sed支持扩展正则表达式。

-i表示直接修改读取的文件内容,而不是输出到终端。

n1,n2表示选择要进行处理的行,不是必需的。10,20表示在10~20行之间处理。

sed格式中的动作支持如下参数。

a:表示添加,后接字符串,添加到当前行的下一行。

c:表示替换,后接字符串,用它替换n1到n2之间的行。

d:表示删除符合模式的行,它的语法为sed '/regexp/d',//之间是正则表达式,模式在d前面,d后面一般不接任何内容。

i:表示插入,后接字符串,添加到当前行的上一行。

p:表示打印,打印选择的某个数据,通常与-n(安静模式)一起使用。

s:表示搜索,还可以替换,类似于vim里的搜索替换功能。例如:“1,20s/old/new/g”表示将1~20行的old替换为new,g在这里表示处理这一行所有匹配的内容。

动作最好用' '括起来,防止因空格导致错误。

sed的基础实例如下(下面的所有实例在CentOS 6.4 x86_64下已通过,这里提前将/etc/passwd拷贝到/tmp目录下)。

1)显示 passwd内容,将2~5行删除后显示,命令如下所示:

cat -n /tmp/passwd |sed '2,5d'

     1 root:x:0:0:root:/root:/bin/bash

     6 sync:x:5:0:sync:/sbin:/bin/sync

     7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

     8 halt:x:7:0:halt:/sbin:/sbin/halt

     9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin

    10 uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin

    11 operator:x:11:0:operator:/root:/sbin/nologin

    12 games:x:12:100:games:/usr/games:/sbin/nologin

    13 gopher:x:13:30:gopher:/var/gopher:/sbin/nologin

    14 ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin

    15 nobody:x:99:99:Nobody:/:/sbin/nologin

    16 vcsa:x:69:69:virtual console memory owner:/dev:/sbin/nologin

    17 saslauth:x:499:76:"Saslauthd user":/var/empty/saslauth:/sbin/nologin

    18 postfix:x:89:89::/var/spool/postfix:/sbin/nologin

    19 sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin

    20 puppet:x:52:52:Puppet:/var/lib/puppet:/sbin/nologin

    21 ntp:x:38:38::/etc/ntp:/sbin/nologin

    22 nagios:x:500:500::/home/nagios:/bin/bash

    23 apache:x:48:48:Apache:/var/www:/sbin/nologin

    24 nginx:x:498:499:nginx user:/var/cache/nginx:/sbin/nologin

2)在第2行后面的一行加上“Hello,world”字符串,命令如下所示:

cat -n /tmp/passwd |sed '2a Hello,world'

显示结果如下所示:

     1 root:x:0:0:root:/root:/bin/bash

     2 bin:x:1:1:bin:/bin:/sbin/nologin

Hello,world

     3 daemon:x:2:2:daemon:/sbin:/sbin/nologin

     4 adm:x:3:4:adm:/var/adm:/sbin/nologin

     5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

     6 sync:x:5:0:sync:/sbin:/bin/sync

     7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

     8 halt:x:7:0:halt:/sbin:/sbin/halt

     9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin

    10 uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin

    11 operator:x:11:0:operator:/root:/sbin/nologin

    12 games:x:12:100:games:/usr/games:/sbin/nologin

    13 gopher:x:13:30:gopher:/var/gopher:/sbin/nologin

    14 ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin

    15 nobody:x:99:99:Nobody:/:/sbin/nologin

    16 vcsa:x:69:69:virtual console memory owner:/dev:/sbin/nologin

    17 saslauth:x:499:76:"Saslauthd user":/var/empty/saslauth:/sbin/nologin

    18 postfix:x:89:89::/var/spool/postfix:/sbin/nologin

    19 sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin

    20 puppet:x:52:52:Puppet:/var/lib/puppet:/sbin/nologin

    21 ntp:x:38:38::/etc/ntp:/sbin/nologin

    22 nagios:x:500:500::/home/nagios:/bin/bash

    23 apache:x:48:48:Apache:/var/www:/sbin/nologin

    24 nginx:x:498:499:nginx user:/var/cache/nginx:/sbin/nologin

3)在第2行后面一行加上两行字,例如:“this is first line!”和 “this is second line!”,命令如下所示:

cat -n /tmp/passwd |sed '2a This is first line! \   //使用续航符\后按回车输入后续行

> This is second line!'     // 以' 再回车结束

显示结果如下所示:

     1 root:x:0:0:root:/root:/bin/bash

     2 bin:x:1:1:bin:/bin:/sbin/nologin

This is first line! 

This is second line!

     3 daemon:x:2:2:daemon:/sbin:/sbin/nologin

     4 adm:x:3:4:adm:/var/adm:/sbin/nologin

     5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

     6 sync:x:5:0:sync:/sbin:/bin/sync

     7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

     8 halt:x:7:0:halt:/sbin:/sbin/halt

     9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin

    10 uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin

    11 operator:x:11:0:operator:/root:/sbin/nologin

    12 games:x:12:100:games:/usr/games:/sbin/nologin

    13 gopher:x:13:30:gopher:/var/gopher:/sbin/nologin

    14 ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin

    15 nobody:x:99:99:Nobody:/:/sbin/nologin

    16 vcsa:x:69:69:virtual console memory owner:/dev:/sbin/nologin

    17 saslauth:x:499:76:"Saslauthd user":/var/empty/saslauth:/sbin/nologin

    18 postfix:x:89:89::/var/spool/postfix:/sbin/nologin

    19 sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin

    20 puppet:x:52:52:Puppet:/var/lib/puppet:/sbin/nologin

    21 ntp:x:38:38::/etc/ntp:/sbin/nologin

    22 nagios:x:500:500::/home/nagios:/bin/bash

    23 apache:x:48:48:Apache:/var/www:/sbin/nologin

    24 nginx:x:498:499:nginx user:/var/cache/nginx:/sbin/nologin

4)将2~5行的内容替换成“I am a good man!”,命令如下所示:

cat -n /tmp/passwd | sed '2,5c I am a good man!'

显示结果如下所示:

     1 root:x:0:0:root:/root:/bin/bash

I am a good man! 

     6 sync:x:5:0:sync:/sbin:/bin/sync

     7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

     8 halt:x:7:0:halt:/sbin:/sbin/halt

     9 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin

    10 uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin

    11 operator:x:11:0:operator:/root:/sbin/nologin

    12 games:x:12:100:games:/usr/games:/sbin/nologin

    13 gopher:x:13:30:gopher:/var/gopher:/sbin/nologin

    14 ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin

    15 nobody:x:99:99:Nobody:/:/sbin/nologin

    16 vcsa:x:69:69:virtual console memory owner:/dev:/sbin/nologin

    17 saslauth:x:499:76:"Saslauthd user":/var/empty/saslauth:/sbin/nologin

    18 postfix:x:89:89::/var/spool/postfix:/sbin/nologin

    19 sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin

    20 puppet:x:52:52:Puppet:/var/lib/puppet:/sbin/nologin

    21 ntp:x:38:38::/etc/ntp:/sbin/nologin

    22 nagios:x:500:500::/home/nagios:/bin/bash

    23 apache:x:48:48:Apache:/var/www:/sbin/nologin

    24 nginx:x:498:499:nginx user:/var/cache/nginx:/sbin/nologin

5)只显示5~7行,注意p 与-n的配合使用,命令如下所示:

cat -n /etc/passwd |sed -n '5,7p'

显示结果如下所示:

     5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

     6 sync:x:5:0:sync:/sbin:/bin/sync

     7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

6)使用ifconfig列出IP,我们只想要eth0的IP地址,因此可以先用ifconfig eth0查看网卡eth0的地址,命令如下所示:

ifconfig eth0

显示结果如下所示:

eth0      Link encap:Ethernet  HWaddr 00:16:3E:7F:67:C3  

          inet addr:192.168.1.207  Bcast:192.168.1.255  Mask:255.255.255.0

          inet6 addr: fe80::216:3eff:fe7f:67c3/64 Scope:Link

          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

          RX packets:11168717 errors:0 dropped:0 overruns:0 frame:0

          TX packets:83863 errors:0 dropped:0 overruns:0 carrier:0

          collisions:0 txqueuelen:1000 

          RX bytes:659653034 (629.0 MiB)  TX bytes:36972729 (35.2 MiB)

          Interrupt:24 


lo        Link encap:Local Loopback  

          inet addr:127.0.0.1  Mask:255.0.0.0

          inet6 addr: ::1/128 Scope:Host

          UP LOOPBACK RUNNING  MTU:16436  Metric:1

          RX packets:34 errors:0 dropped:0 overruns:0 frame:0

          TX packets:34 errors:0 dropped:0 overruns:0 carrier:0

          collisions:0 txqueuelen:0 

          RX bytes:2716 (2.6 KiB)  TX bytes:2716 (2.6 KiB)

我们可以先用grep 取出有IP的那一行,然后用sed去掉(替换成空)IP前面和后面的内容,命令如下所示:

ifconfig eth0 | grep "inet addr" | sed 's/^.*addr://g' | sed 's/Bcast.*$//g'

命令显示结果如下所示:

172.30.171.35 

这里解释下这行组合命令:

grep 后面紧跟"inet addr"是为了单独捕获包含IPv4的那行内容;'^.*addr:' 表示从开头到addr:的字符串,将它替换为空;'Bcast.*$' 表示从Bcast到结尾的串,也将它替换为空,然后就只剩下IPv4地址。

另外一种更简便的方法是使用awk,命令如下所示:

ifconfig eth0 | grep "inet addr:"|awk -F[:" "]+ '{print $4}'

命令显示结果如下所示:

192.168.1.207

awk -F[:" "]的意思就是以“:”或空格符作为分隔符,然后打印出第4列,这里有些朋友可能会有疑惑:为什么不直接以如下方法来获取IP呢:

ifconfig eth0 | grep "inet addr:" | awk -F: '{print $2}'

大家可以看下结果,得出的结果是:

192.168.1.207  Bcast

所以还需要再进行一步操作,如下:

ifconfig eth0 | grep "inet addr:" | awk -F: '{print $2}' | awk '{print $1}'

希望大家通过这个例子好好总结一下sed的经典用法,第二种方法其实是awk的方法,awk也是一种优秀的编辑器,现多用于截取文本字段的列。

7)在/etc/man.config中,将有man的设置取出,但不要说明内容。命令如下所示:

cat /etc/man.config |grep 'MAN'|sed 's/#.*$//g'|sed '/^$/d'

显示结果如下所示:

MANPATH /usr/man

MANPATH /usr/share/man

MANPATH /usr/local/man

MANPATH /usr/local/share/man

MANPATH /usr/X11R6/man

MANPATH_MAP /bin /usr/share/man

MANPATH_MAP /sbin /usr/share/man

MANPATH_MAP /usr/bin /usr/share/man

MANPATH_MAP /usr/sbin /usr/share/man

MANPATH_MAP /usr/local/bin /usr/local/share/man

MANPATH_MAP /usr/local/sbin /usr/local/share/man

MANPATH_MAP /usr/X11R6/bin /usr/X11R6/man

MANPATH_MAP /usr/bin/X11 /usr/X11R6/man

MANPATH_MAP /usr/bin/mh /usr/share/man

MANSECT 1:1p:8:2:3:3p:4:5:6:7:9:0p:n:l:p:o:1x:2x:3x:4x:5x:6x:7x:8x

注意,#不一定要出现在行首。 因此,/#.*$/表示#和后面的数据(直到行尾)是一行注释,将它们替换成空。/^$/ 表示空行,后接d 表示删除空行。

删除空行不能用替换方法,因为空行替换成空后,还是有换行符在那一行中。

以上就是sed的几种常见的语法命令,希望大家结合下面的实例,多在自己的机器上演练,以尽快熟练掌握其用法。

2.4.2 sed的用法示例

1. sed的基础用法

1)删除行首空格,有下面几种方法,代码分别如下所示:

sed 's/^[ ]*//g' filename

sed 's/^ *//g' filename

sed 's/^[[:space:]]*//g' filename

2)在行后和行前添加新行。

行后的添加命令如下所示:

sed 's/pattern/&\n/g' filename

行前的添加命令如下所示:

sed 's/pattern/\n&/g' filename

其中,&代表pattern。

3)使用变量替换(使用双引号),代码如下:

sed -e "s/$var1/$var2/g" filename

4)在第一行前插入文本,代码如下:

sed -i '1 i\插入字符串' filename

5)在最后一行插入字符串,代码如下:

sed -i '$ a\插入字符串' filename

6)在匹配行前插入字符串,代码如下:

sed -i '/pattern/ i "插入字符串"' filename

7)在匹配行后插入字符串,代码如下:

sed -i '/pattern/ a "插入字符串"' filename

8)删除文本中的空行、以空格组成的行及#号注释的行,代码如下:

grep -v ^# filename | sed /^[[:space:]]*$/d | sed /^$/d

9)将目录/modules下面所有文件中的zhangsan都修改成list(注意备份原文件),代码如下:

sed -i 's/zhangsan/list/g' `grep zhangsan -rl /modules`

2.巧用vim+sed整理nginxd.txt脚本文件

笔者曾在工作中遇到过一个问题,需要使用Nginx配置脚本来解决,但在复制到服务器中并运行时发现前面的001~100行都有行标识符,外带空格,影响运行和美观。本来想逐行删除的,后来觉得过于麻烦,于是想到了用sed来解决问题,解决方法如下。

1)先在 vim里删除所有行的首数字,命令如下所示:

:%s/^[0-9][0-9]* // 

2)然后删除所有行的首空字符,命令如下所示:

sed -i 's/^[[:space:]]*//' nginxd.sh

整个nginxd.txt演示脚本如下,有兴趣的朋友也可以拿来练下手。

001    #!/bin/sh

002     

003    # source function library

004    . /etc/rc.d/init.d/functions

005     

006    # Source networking configuration.

007    . /etc/sysconfig/network

008     

009    # Check that networking is up.

010    [ ${NETWORKING} = "no" ] && exit 0

011     

012    RETVAL=0

013    prog="nginx"

014     

015    nginxDir=/usr/local/nginx

016    nginxd=$nginxDir/sbin/nginx

017    nginxConf=$nginxDir/conf/nginx.conf

018    nginxPid=$nginxDir/nginx.pid

019     

020    nginx_check()

021    {

022        if [[ -e $nginxPid ]]; then

023            ps aux |grep -v grep |grep -q nginx

024            if (( $? == 0 )); then

025                echo "$prog already running..."

026                exit 1

027            else

028                rm -rf $nginxPid &> /dev/null

029            fi

030        fi

031    }

032     

033    start()

034    {

035        nginx_check

036        if (( $? != 0 )); then

037            true

038        else

039            echo -n $"Starting $prog:"

040            daemon $nginxd -c $nginxConf

041            RETVAL=$?

042            echo

043            [ $RETVAL = 0 ] && touch /var/lock/subsys/nginx

044            return $RETVAL

045        fi

046    }

047     

048    stop()

049    {

050        echo -n $"Stopping $prog:"

051        killproc $nginxd

052        RETVAL=$?

053        echo

054        [ $RETVAL = 0 ] && rm -f /var/lock/subsys/nginx $nginxPid

055    }

056     

057    reload()

058    {

059        echo -n $"Reloading $prog:"

060        killproc $nginxd -HUP

061        RETVAL=$?

062        echo

063    }

064     

065    monitor()

066    {

067        status $prog &> /dev/null

068        if (( $? == 0 )); then

069            RETVAL=0

070        else

071            RETVAL=7

072        fi

073    }

074     

075    case "$1" in

076            start)

077                    start

078                    ;;

079            stop)

080                    stop

081                    ;;

082            restart)

083                    stop

084                    start

085                    ;;

086            reload)

087                    reload

088                    ;;

089            status)

090                    status $prog

091                    RETVAL=$?

092                    ;;

093            monitor)

094                    monitor

095                    ;;

096            *)

097                    echo $"Usage: $0 {start|stop|restart|reload|status|monitor}"

098                    RETVAL=1

099    esac

100    exit $RETVA

脚本下载地址:https://github.com/yuhongchun/automation。

此文件还有很多变化,比如空格在开头,序列号在中间,也可以用sed来解决,不过应该写出怎样的sed命令来解决,就留给大家来思考吧!

3. sed结合正则表达式批量修改文件名

笔者曾在工作中遇到了更改文件的需求,原来某文件test.txt中的链接地址为:

http://www.5566.com/produce/2007080412/315613171.shtml

http://bz.5566.com/produce/20080808/311217.shtml

http://gz.5566.com/produce/20090909/311412.shtml

现要求将http://*.5566.com更改为/home/html/www.5566.com,于是用sed结合正则表达式来解决这个问题,命令如下所示:

sed -i 's/http.*\.com/home\/html\/www.5566.com/g' test.txt

如果是用纯sed命令,方法更简单,如下所示:

sed -i 's@http://[^.]*.5566.com@/home/html/www.5566.com@g' test.txt 

sed是完全支持正则表达式的,在正则表达式里,[^.]表示为非.的所有字符,换成[^/]也可以;另外,@是sed的分隔符,我们也可以用其他符号,比如/,但是如果要用到/的话就得表示成\/了,所以笔者经常用的方法是采用@作为分隔符。

4.在配置.conf文件时,为相邻的几行添加#号

例如,我们要将test.txt文件中的31~36行加上#号,使这部分内容暂时失效,该如何实现呢?

在vim中,可以执行如下命令:

:31,36 s/^/#/

而使用sed执行更加方便,命令如下所示:

sed -i '31,36s/^/#/' test.txt

反之,如果要将31~36行带#号的全部删除,用sed又该如何实现呢?方法如下:

sed –i ‘31,36s/^#//’ test.txt

许多人习惯在这个方法后面带个g,事实上,如果没有g,则表示从行的左端开始匹配,每一行第一个与之匹配的都会被换掉;如果有g,则表示每一行所有与之匹配的都会被换掉。

5.利用sed分析日志

利用sed还可以很方便地分析日志。例如,在以下的secure日志文件中,想用sed抓取12:48:48至12:48:55的日志:

Apr 17 05:01:20 localhost sshd[16375]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=222.186.37.226  user=root

Apr 17 05:01:22 localhost sshd[16375]: Failed password for root from 222.186.37.226 port 60700 ssh2

Apr 17 05:01:22 localhost sshd[16376]: Received disconnect from 222.186.37.226: 11: Bye Bye

Apr 17 05:01:22 localhost sshd[16377]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=222.186.37.226  user=root

Apr 17 05:01:24 localhost sshd[16377]: Failed password for root from 222.186.37.226 port 60933 ssh2

Apr 17 05:01:24 localhost sshd[16378]: Received disconnect from 222.186.37.226: 11: Bye Bye

Apr 17 05:01:24 localhost sshd[16379]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=222.186.37.226  user=root

Apr 17 05:01:26 localhost sshd[16379]: Failed password for root from 22.186.37.226 port 32944 ssh2

Apr 17 05:01:26 localhost sshd[16380]: Received disconnect from 222.186.37.226: 11: Bye Bye

Apr 17 05:01:27 localhost sshd[16381]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=222.186.37.226  user=root

Apr 17 05:01:29 localhost sshd[16381]: Failed password for root from 222.186.37.226 port 33174 ssh2

Apr 17 05:01:29 localhost sshd[16382]: Received disconnect from 222.186.37.226: 11: Bye Bye

Apr 17 05:01:29 localhost sshd[16383]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=222.186.37.226  user=root

Apr 17 05:01:31 localhost sshd[16383]: Failed password for root from 222.186.37.226 port 33474 ssh2

Apr 17 05:01:31 localhost sshd[16384]: Received disconnect from 222.186.37.226: 11: Bye Bye

Apr 17 05:01:32 localhost sshd[16385]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=222.186.37.226  user=root

可以利用sed截取日志命令,如下所示:

cat /var/log/secure | sed -n '/12:48:48/,/12:48:55/p'

脚本显示结果如下所示:

Apr 23 12:48:48 localhost sshd[20570]: Accepted password for root from 220.249.72.138 port 27177 ssh2

Apr 23 12:48:48 localhost sshd[20570]: pam_unix(sshd:session): session opened for user root by (uid=0)

Apr 23 12:48:55 localhost sshd[20601]: Accepted password for root from 220.249.72.138 port 59754 ssh2

sed的用法还有许多,这就要靠大家在日常工作中归纳总结了。有兴趣的朋友还可以多了解下awk的用法,我们在工作中要频繁地分析日志文件,awk+sed是比较好的选择,下面介绍下awk的基本使用方法。

2.5 awk的基础用法及实用示例

1. awk工具简介

awk是一个强大的文本分析工具,相对于grep的查找、sed的编辑,awk在对数据进行分析并生成报告时,显得尤为强大。简单来说,awk就是把文件逐行地读入,然后以空格为默认分隔符将每行进行切片,切开的部分再进行各种分析处理。awk的名称得自于它的创始人Alfred Aho、Peter Weinberger 和Brian Kernighan 姓氏的首个字母。实际上awk的确拥有自己的语言:awk程序设计语言,三位创建者已将它正式定义为“样式扫描和处理语言”。

awk允许我们创建简短的程序,这些程序可读取输入文件、为数据排序、处理数据、对输入执行计算及生成报表,还有无数其他的功能。

2.使用方法 

awk的命令格式如下:

awk 'pattern {action}' filename

其中,pattern就是要表示的正则表达式,它表示awk在数据中查找的内容,而 action 是在找到匹配内容时所执行的一系列命令。

awk语言的最基本功能是在文件或字符串中基于指定的规则浏览和抽取信息,在抽取信息后,才能进行其他文本操作。完整的awk脚本通常用来格式化文本文件中的信息。

通常,awk是以文件的一行为处理单位的。awk每接收文件的一行后,就会执行相应的命令来处理文本。

下面介绍一下awk程序设计模型。

awk程序由3部分组成,分别为:

初始化(处理输入前做的准备,放在BEGIN块中)

数据处理(处理输入数据)

收尾处理(处理输入完成后要进行的处理,放到END块中)

其中,在“数据处理”过程中,指令被写成一系列模式/动作过程,模式用于测试输入行的规则,以确定是否将规则应用于这些输入行。

3. awk调用方式

awk主要有三种调用方式,下面分别来看看。

(1)命令行方式 

awk [-F  field-separator] 'commands' filename

其中,commands是真正的awk命令,[-F域分隔符]是可选的,filename是待处理的文件。

在awk文件的各行中,由域分隔符分开的每一项称为一个域。通常,在不指名-F域分隔符的情况下,默认的域分隔符是空格。

(2)使用-f选项调用awk 程序

awk允许将一段awk 程序写入一个文本文件中,然后在awk 命令行中用-f 选项调用并执行这段程序,命令如下:

awk -f awk-script-file filename 

其中,-f选项加载awk-script-file中的awk脚本,filename表示文件名。 

(3)利用命令解释器调用awk 程序

利用Linux系统支持的命令解释器功能可以将一段awk 程序写入文本文件中,然后在它的第一行加上#!/bin/awk –f。

4. awk详细语法

与其他Linux命令一样,awk 拥有自己的语法:

awk [ -F re] [parameter...] ['prog'] [-f progfile][in_file...]

其中,

-F re:允许awk更改其字段分隔符。

parameter:该参数帮助为不同的变量赋值。

prog:awk的程序语句段。这个语句段必须用单引号'和'括起,以防被Shell解释。

前面已经提到过这个程序语句段的标准形式,如下所示:

awk 'pattern {action}' filename

其中pattern 参数可以是egrep 正则表达式中的任何一个,它可以使用语法/re/再加上一些样式匹配技巧构成。与sed 类似,也可以使用“,”分开两种样式以选择某个范围。

action参数总是被大括号包围,它由一系列awk语句组成,各语句之间用“;”分隔。awk会解释它们,并在pattern给定的样式匹配记录上执行相关操作。

事实上,在使用该命令时可以省略pattern和action两者中的某一个,但不能两者同时省略。省略pattern表示没有样式匹配,对所有行(记录)均执行操作;省略action表示执行默认的操作—在标准输出上显示。

-f progfile:允许awk 调用并执行progfile指定的程序文件。progfile是一个文本文件,它必须符合awk 的语法。

in_file:awk的输入文件,awk 允许对多个输入文件进行处理。值得注意的是awk 不修改输入文件。

如果未指定输入文件,awk 将接受标准输入,并将结果显示在标准输出上。

5. awk脚本编写

(1)awk的内置变量

awk的内置变量主要有如下几种。

FS:输入数据的字段分隔符。

RS:输入数据的记录分隔符。

OFS:输出数据的字段分隔符。

ORS:输出数据的记录分隔符。另一类是系统自动改变的,比如:NF表示当前记录的字段个数,NR表示当前记录的编号等。

举个例子,可用如下命令打印passwd中的第1个和第3个字段:

awk -F ":" '{ print $1 "" $3 }'  /tmp/passwd

(2)pattern/action模式

awk程序部分采用了pattern/action模式,即针对匹配pattern的数据,使用action逻辑进行处理。来看下面这两个例子。

判断当前是不是空格,命令如下:

/^$/ {print "This is a blank line!"} 

判断第5个字段是否含有“MA”,命令如下:

$5 ~ /MA/ {print $1 "," $3} 

(3)awk与Shell混用

因为awk可以作为一个Shell命令使用,因此awk能与Shell脚本程序很好地融合在一起,这点为实现awk与Shell程序的混合编程提供了可能。实现混合编程的关键是awk与Shell脚本之间的对话,换言之,就是awk与Shell 脚本之间的信息交流:awk从Shell脚本中获取所需的信息(通常是变量的值)、在awk 中执行Shell命令行、Shell脚本将命令执行的结果送给awk处理,以及Shell脚本读取awk的执行结果等,另外需要注意的是在Shell脚本中读取awk变量的方式,一般是通过" '$变量名' "的方式来读取Shell程序中的变量。

6. awk内置变量

awk有许多内置变量用于设置环境信息,这些变量可以被改变,下面列举了工作中最常用的一些awk变量,变量及其意义如下所示:

ARGC               命令行参数个数

ARGV               命令行参数排列

ENVIRON            支持队列中系统环境变量的使用

FILENAME           awk浏览的文件名

FNR                浏览文件的记录数

FS                 设置输入域分隔符,等价于命令行 -F选项

NF                 浏览记录的域的个数

NR                 已读的记录数

OFS                输出域分隔符

ORS                输出记录分隔符

RS                 控制记录分隔符

此外,$0变量是指整条记录。$1表示当前行的第一个域,$2表示当前行的第二个域……依次类推。

7. awk中的print和printf 

awk中同时提供了print和printf两种用于打印输出的函数。 

其中print函数的参数可以是变量、数值或字符串。字符串必须用双引号引用,参数用逗号分隔。如果没有逗号,参数就会串联在一起而无法区分。这里,逗号的作用与输出文件的分隔符的作用是一样的,只是后者是空格而已。

printf函数,其用法和C语言中printf函数基本相似,可以格式化字符串,输出复杂结果时,printf的显示结果更加人性化。 

使用示例如下所示:

awk -F ':' '{printf("filename:%10s,linenumber:%s,columns:%s,linecontent:%s\ n",FILENAME,NR,NF,$0)}' /tpm/passwd  

命令显示结果如下所示:

filename:/tmp/passwd,linenumber:1,columns:7,linecontent:root:x:0:0:root:/root:/bin/bash

filename:/tmp/passwd,linenumber:2,columns:7,linecontent:bin:x:1:1:bin:/bin:/sbin/nologin

filename:/tmp/passwd,linenumber:3,columns:7,linecontent:daemon:x:2:2:daemon:/sbin:/sbin/nologin

filename:/tmp/passwd,linenumber:4,columns:7,linecontent:adm:x:3:4:adm:/var/adm:/sbin/nologin

filename:/tmp/passwd,linenumber:5,columns:7,linecontent:lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

filename:/tmp/passwd,linenumber:6,columns:7,linecontent:sync:x:5:0:sync:/sbin:/bin/sync

filename:/tmp/passwd,linenumber:7,columns:7,linecontent:shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

filename:/tmp/passwd,linenumber:8,columns:7,linecontent:halt:x:7:0:halt:/sbin:/sbin/halt

filename:/tmp/passwd,linenumber:9,columns:7,linecontent:mail:x:8:12:mail:/var/spool/mail:/sbin/nologin

filename:/tmp/passwd,linenumber:10,columns:7,linecontent:uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin

filename:/tmp/passwd,linenumber:11,columns:7,linecontent:operator:x:11:0:operator:/root:/sbin/nologin

filename:/tmp/passwd,linenumber:12,columns:7,linecontent:games:x:12:100:games:/usr/games:/sbin/nologin

filename:/tmp/passwd,linenumber:13,columns:7,linecontent:gopher:x:13:30:gopher:/var/gopher:/sbin/nologin

filename:/tmp/passwd,linenumber:14,columns:7,linecontent:ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin

filename:/tmp/passwd,linenumber:15,columns:7,linecontent:nobody:x:99:99:Nobody:/:/sbin/nologin

filename:/tmp/passwd,linenumber:16,columns:7,linecontent:vcsa:x:69:69:virtual console memory owner:/dev:/sbin/nologin

filename:/tmp/passwd,linenumber:17,columns:7,linecontent:saslauth:x:499:76:"Saslauthd user":/var/empty/saslauth:/sbin/nologin

filename:/tmp/passwd,linenumber:18,columns:7,linecontent:postfix:x:89:89::/var/spool/postfix:/sbin/nologin

filename:/tmp/passwd,linenumber:19,columns:7,linecontent:sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin

filename:/tmp/passwd,linenumber:20,columns:7,linecontent:puppet:x:52:52:Puppet:/var/lib/puppet:/sbin/nologin

filename:/tmp/passwd,linenumber:21,columns:7,linecontent:ntp:x:38:38::/etc/ntp:/sbin/nologin

filename:/tmp/passwd,linenumber:22,columns:7,linecontent:nagios:x:500:500::/home/nagios:/bin/bash

filename:/tmp/passwd,linenumber:23,columns:7,linecontent:apache:x:48:48:Apache:/var/www:/sbin/nologin

filename:/tmp/passwd,linenumber:24,columns:7,linecontent:nginx:x:498:499:nginx user:/var/cache/nginx:/sbin/nologin

参考文档http://blog.pengduncun.com/?p=876。

8.工作示例

截取出init 中PID的示例命令如下:

ps -aux | grep init | grep -v grep | awk '{print $2}' 

截取网卡eth0的IPv4地址,示例命令如下:

ifconfig eth0 |grep "inet addr:" | awk -F: '{print $2}' |awk '{print $1}'

找出当前系统的自启动服务,示例命令如下:

chkconfig --list |grep 3:on | awk '{print $1}'

取出vmstat第4项的平均值,示例命令如下:

vmstat 1 4 | egrep -v "io|free" | awk '{sum+=$4} END{print sum/4}'

以|为分隔符,汇总/yundisk/log/hadoop/下的hadoop第9项日志并打印,示例命令如下:

cat /yundisk/log/hadoop/hadoop_clk_*.log | awk -F '|' 'BEGIN{count=0} $2>0 {count=count+$9} END {print count}'

2.6 生产环境下的Shell和Python脚本分类

生产环境下的Shell和Python脚本的作用还是挺多的,这里根据2.1节所介绍的日常工作中Shell脚本的作用,将生产环境下的Shell脚本分为备份类、监控类、统计类、运维开发类和自动化运维类。前面3类从字面意义上看比较容易理解,后面的两类需要稍微解释一下,运维开发类脚本是利用Shell或Python实现一些非系统类的管理工作,比如SVN的发布程序等;而自动化运维类脚本则是利用Shell或Python来自动替我们做一些烦琐的工作,比如自动生成并分配密码给开发组的用户,或者自动安装LNMP环境等。下面会就这些分类列举一些具体的实例,以便于大家理解。另外值得说明的一点是,这些实例都源自于笔者的线上环境,大家拿过来稍微改动一下IP或备份一下目录基本上就可以直接使用了。

另外,因为现在的线上业务大多采用的是AWS EC2机器,基本上采用的都是Amazon Linux系统,所以这里先跟大家简单介绍下Amazon Linux系统。

Amazon Linux系统由Amazon Web Services (AWS)提供,旨在为Amazon EC2 上运行的应用程序提供稳定、安全、高性能的执行环境。此外,它还包括能够与AWS轻松集成的软件包,比如启动配置工具和许多常见的AWS库及工具等。AWS为运行Amazon Linux系统的所有实例提供持续的安全性和维护更新。

(1)启动并连接到Amazon Linux实例

要启动Amazon Linux实例,请使用 Amazon Linux AMI(映像)。AWS向Amazon EC2 用户提供Amazon Linux AMI,无需额外费用。找到需要的AMI后,记下AMI ID,然后就可以使用AMI ID 来启动并连接到相应的实例了。

默认情况下,Amazon Linux不支持远程根SSH。此外,密码验证已禁用,以防止强力 (brute-force)密码攻击。要在 Amazon Linux 实例上启用 SSH 登录,必须在实例启动时为其提供密钥对,还必须设置用于启动实例的安全组以允许 SSH 访问。默认情况下,唯一可以使用 SSH 进行远程登录的账户是 ec2-user;此账户还拥有 sudo 特权。如果希望启动远程根登录,请注意,其安全性不及依赖密钥对和二级用户。

有关启动和使用 Amazon Linux 实例的信息,请参阅启动实例。有关连接到Amazon Linux实例的更多信息,请参阅连接到Linux实例。

(2)识别 Amazon Linux AMI 映像

每个映像都包含唯一的 /etc/image-id,用于识别 AMI。此文件包含了有关映像的信息。

下面是/etc/image-id文件示例,命令如下:

cat /etc/image-id

命令显示结果如下所示:

image_name="amzn-ami-hvm"

image_version="2015.03"

image_arch="x86_64"

image_file="amzn-ami-hvm-2015.03.0.x86_64.ext4.gpt"

image_stamp="366c-fff6"

image_date="20150318153038"

recipe_name="amzn ami"

recipe_id="1c207c1f-6186-b5c9-4e1b-9400-c2d8-a3b2-3d11fdf8"

其中,image_name、image_version 和 image_arch 项目来自 Amazon 用于构建映像的配方。image_stamp 只是映像创建期间随机生成的唯一十六进制值。image_date项目的格式为 YYYYMMDDhhmmss,是映像创建时的 UTC 时间。recipe_name 和 recipe_id是Amazon用于构建映像的构建配方的名称和ID,用于识别当前运行的 Amazon Linux 的版本。从yum存储库安装更新时,此文件不会更改。

Amazon Linux 包含 /etc/system-release 文件,用于指定当前安装的版本。此文件通过 yum 进行更新,是 system-release RPM 的一部分。

下面是 /etc/system-release 文件示例,命令如下:

cat /etc/system-release

命令显示结果如下所示:

Amazon Linux AMI release 2015.03

Amazon Linux系统这部分内容摘录自http://docs.aws.amazon.com/zh_cn/AWSEC2/latest/UserGuide/AmazonLinuxAMIBasics.html#IdentifyingLinuxAMI_Images

2.6.1 备份类脚本

俗话说得好,备份是救命的稻草。特别是重要的数据和代码,这些都是公司的重要资产,所以备份是必须的。备份能在我们不慎执行了一些毁灭性的工作之后(比如不小心删除了数据),进行恢复工作。许多有实力的公司在国内好几个地方都设有灾备机房,而且用的都是价格不菲的EMC高端存储。可能会有朋友要问:如果我们没有存储怎么办?这点可以参考一下笔者公司的备份策略,即:在执行本地备份的同时,让Shell脚本自动上传数据到另一台FTP备份服务器中,这种异地备份策略成本比较小,无须存储,而且安全系统高,相当于双备份,本地和异地同时出现数据损坏的概率几乎是不可能的。

此双备份策略的具体步骤如下。

首先,做好准备工作。先安装一台CentOS 6.4 x86_64的备份服务器,并安装vsftpd服务,稍微改动一下配置后启动。另外,关于vsftpd的备份目录,可以选择做RAID1或RAID5的分区或存储。

vsftpd服务的安装如下,CentOS 6.4 x86_64下自带的yum极为方便。

yum -y install vsftpd

service vsftpd start

chkconfig vsftpd on

vsftpd的配置比较简单,详细语法略过,这里只给出配置文件,可以通过组合使用如下命令直接得出vsftpd.conf中有效的文件内容:

grep -v "^#" /etc/vsftpd/vsftpd.conf | grep -v '^$'

local_enable=YES

write_enable=YES

local_umask=022

dirmessage_enable=YES

xferlog_enable=YES

connect_from_port_20=YES

xferlog_std_format=YES

listen=YES

chroot_local_user=YES

pam_service_name=vsftpd

userlist_enable=YES

tcp_wrappers=YES

chroot_local_user=YES这条语句需要重点强调一下。它的作用是对用户登录权限进行限制,即所有本地用户登录vsftpd服务器时只能在自己的家目录下,这是基于安全的考虑,笔者在编写脚本的过程中也考虑到了这点,如果大家要移植此脚本到自己的工作环境中,不要忘了这条语句,不然的话异地备份极有可能会失效。

另外,我们在备份服务器上应该建立备份用户,例如svn,并为其分配密码,还应该将其家目录更改为备份目录,即/data/backup/svn-bakcup,这样的话更方便备份工作,以下备份脚本依此类推。

1.版本控制软件SVN代码库的备份脚本

版本控制软件SVN的重要性在这里就不再多说了,现在很多公司基本还是利用SVN作为提交代码集中管理的工具,所以做好其备份工作的重要性就不言而喻了。这里的轮询周期为30天一次,Shell会自动删除30天以前的文件。在vsftpd服务器上建立相应的备份用户svn的脚本内容如下(此脚本在CentOS 5.8 x86_64下已测试通过):

#!/bin/sh

SVNDIR=/data/svn

SVNADMIN=/usr/bin/svnadmin

DATE=`date +%Y-%m-%d`

OLDDATE=`date +%Y-%m-%d -d '30 days'`

BACKDIR=/data/backup/svn-backup


[ -d ${BACKDIR} ] || mkdir -p ${BACKDIR}

LogFile=${BACKDIR}/svnbak.log

[ -f ${LogFile} ] || touch ${LogFile}

mkdir ${BACKDIR}/${DATE}



for PROJECT in myproject official analysis mypharma

do

  cd $SVNDIR

  $SVNADMIN hotcopy $PROJECT  $BACKDIR/$DATE/$PROJECT --clean-logs

  cd $BACKDIR/$DATE

  tar zcvf ${PROJECT}_svn_${DATE}.tar.gz $PROJECT  > /dev/null

  rm -rf $PROJECT

sleep 2

done


HOST=192.168.2.112

FTP_USERNAME=svn

FTP_PASSWORD=svn101


cd ${BACKDIR}/${DATE}


ftp -i -n -v << !

open ${HOST}

user ${FTP_USERNAME} ${FTP_PASSWORD}

bin

cd ${OLDDATE}

mdelete *

cd ..

rmdir ${OLDDATE}

mkdir ${DATE}

cd ${DATE}

mput *

bye

!

2. MySQL从数据库备份脚本

主MySQL脚本跟其比较类似,主要是用MySQL自带的命令mysqldump 进行备份的。这里要说明的是,本地的轮询周期为20天,vsftpd的轮询周期为60天,备份后就算是用gzip压缩,MySQL的数据库还是很大,大家可以根据实际需求来更改这个时间,注意不要让数据库撑爆你的备份服务器。由于是内部开发环境,密码设置得比较简单,如果要将其移植到自己的公网服务器上,就要在安全方面多注意一下了。另外附带说明的是,--opt只是一个快捷选项,等同于同时添加 --add-drop-tables  --add-locking  --create-option  --disable-keys  --extended-insert  --lock-tables --quick --set-charset 选项。本选项能让mysqldump很快地导出数据,并且导出的数据可以很快被导回。该选项默认是开启的,可以用--skip-opt将其禁用。注意,如果运行mysqldump没有指定 --quick 或 --opt 选项,则会将整个结果集放在内存中。如果导出的是大数据库则可能会出现问题,脚本内容如下(此脚本在CentOS 5.8 x86_64下已测试通过):

#!/bin/bash

USERNAME=mysqlbackup

PASSWORD=mysqlbackup

DATE=`date +%Y-%m-%d`

OLDDATE=`date +%Y-%m-%d -d '-20 days'`

FTPOLDDATE=`date +%Y-%m-%d -d '-60 days'`

MYSQL=/usr/local/mysql/bin/mysql

MYSQLDUMP=/usr/local/mysql/bin/mysqldump

MYSQLADMIN=/usr/local/mysql/bin/mysqladmin

SOCKET=/tmp/mysql.sock

BACKDIR=/data/backup/db


[ -d ${BACKDIR} ] || mkdir -p ${BACKDIR}

[ -d ${BACKDIR}/${DATE} ] || mkdir ${BACKDIR}/${DATE}

[ ! -d ${BACKDIR}/${OLDDATE} ] || rm -rf ${BACKDIR}/${OLDDATE}


for DBNAME in mysql test report

do

   ${MYSQLDUMP} --opt -u${USERNAME} -p${PASSWORD} -S${SOCKET} ${DBNAME} | gzip > ${BACKDIR}/${DATE}/${DBNAME}-backup-${DATE}.sql.gz

   echo "${DBNAME} has been backup successful"

   /bin/sleep 5

done


HOST=192.168.4.45

FTP_USERNAME=dbmysql

FTP_PASSWORD=dbmysql

cd ${BACKDIR}/${DATE}

ftp -i -n -v << !

open ${HOST}

user ${FTP_USERNAME} ${FTP_PASSWORD}

bin

cd ${FTPOLDDATE}

mdelete *

cd ..

rmdir ${FTPOLDDATE}

mkdir ${DATE}

cd ${DATE}

mput *

bye

!

3. MySQL数据备份至S3文件系统 

这里先来介绍下亚马逊的分布式文件系统S3,S3为开发人员提供了一个高可扩展(Scalability)、高持久性(Durability)和高可用(Availability)的分布式数据存储服务。它是一个完全针对互联网的数据存储服务,借助一个简单的Web服务接口就可以通过互联网在任何时候访问S3上的数据。当然存放在S3上的数据要可以进行访问控制以保障数据的安全性。这里所说的访问S3包括读、写、删除等多种操作。在脚本的最后,采用AWS S3命令中的cp将MySQL上传至s3://example-shar这个bucket上面(S3详细资料介绍http://aws.amazon.com/cn/s3/),脚本内容如下所示(此脚本在Amazon Linux AMI x86_64下已测试通过):

#!/bin/bash

#

# Filename:

# backupdatabase.sh

# Description:

# backup cms database and remove backup data before 7 days

# crontab

# 55 23 * * * /bin/sh /yundisk/cms/crontab/backupdatabase.sh >> /yundisk/cms/crontab/backupdatabase.log 2>&1


DATE=`date +%Y-%m-%d`

OLDDATE=`date +%Y-%m-%d -d '-7 days'`


#MYSQL=/usr/local/mysql/bin/mysql

#MYSQLDUMP=/usr/local/mysql/bin/mysqldump

#MYSQLADMIN=/usr/local/mysql/bin/mysqladmin


BACKDIR=/yundisk/cms/database

[ -d ${BACKDIR} ] || mkdir -p ${BACKDIR}

[ -d ${BACKDIR}/${DATE} ] || mkdir ${BACKDIR}/${DATE}

[ ! -d ${BACKDIR}/${OLDDATE} ] || rm -rf ${BACKDIR}/${OLDDATE}


mysqldump --default-character-set=utf8 --no-autocommit --quick --hex-blob --single-transaction -uroot  cms_production  | gzip > ${BACKDIR}/${DATE}/cms-backup-${DATE}.sql.gz

echo "Database cms_production and bbs has been backup successful"

/bin/sleep 5


aws s3 cp ${BACKDIR}/${DATE}/* s3://example-share/cms/databackup/

2.6.2 统计类脚本

统计工作一直是Shell和Python脚本的强项,我们完全可以利用sed、awk再加上正则表达式,写出强大的统计脚本来分析我们的系统日志、安全日志及服务器应用日志等。

1. Nginx负载均衡器日志汇总脚本

以下脚本是用来分析Nginx负载均衡器的日志的,作为Awstats的补充,它可以快速得出排名最前的网站和IP等,脚本内容如下(此脚本在CentOS 5.8/6.4 x86_64下均已测试通过):

#!/bin/bash


if [ $# -eq 0 ]; then

   echo "Error: please specify logfile."

   exit 0

else

   LOG=$1

fi


if [ ! -f $1 ]; then

   echo "Sorry, sir, I can't find this apache log file, pls try again!"

exit 0

fi


####################################################

echo "Most of the ip:"

echo "-------------------------------------------"

awk '{ print $1 }' $LOG | sort | uniq -c | sort -nr | head -10

echo

echo

####################################################

echo "Most of the time:"

echo "--------------------------------------------"

awk '{ print $4 }' $LOG | cut -c 14-18 | sort | uniq -c | sort -nr | head -10

echo

echo

####################################################

echo "Most of the page:"

echo "--------------------------------------------"

awk '{print $11}' $LOG | sed 's/^.*\(.cn*\)\"/\1/g' | sort | uniq -c | sort -rn | head -10

echo

echo

####################################################

echo "Most of the time / Most of the ip:"

echo "--------------------------------------------"

awk '{ print $4 }' $LOG | cut -c 14-18 | sort -n | uniq -c | sort -nr | head -10 > timelog


for i in `awk '{ print $2 }' timelog`

do

   num=`grep $i timelog | awk '{ print $1 }'`

   echo " $i $num"

   ip=`grep $i $LOG | awk '{ print $1}' | sort -n | uniq -c | sort -nr | head -10`

   echo "$ip"

   echo

done

rm -f timelog

2. 探测多节点Web服务质量

工作中有时会出现网络延迟导致程序返回数据不及时的问题,这时就需要精准定位机器是在哪个时间段出现了网络延迟的情况。对此,可以通过Python下的pycurl模块来实现定位,它可以通过调用pycurl提供的方法,来探测Web服务质量,比如了解相应的HTTP状态码、请求延时、HTTP头信息、下载速度等,脚本内容如下所示(此脚本在Amazon Linux AMI x86_64下已测试通过):

#!/usr/bin/python

#encoding:utf-8

#*/30 * * * * /usr/bin/python /root/dnstime.py >> /root/myreport.txt 2>&1

import os

import time

import sys

import pycurl

#import commands

import time


URL="http://imp-east.example.net"

ISOTIMEFORMAT="%Y-%m-%d %X"

c = pycurl.Curl()

c.setopt(pycurl.URL, URL)

c.setopt(pycurl.CONNECTTIMEOUT, 5)

c.setopt(pycurl.TIMEOUT, 5)

c.setopt(pycurl.FORBID_REUSE, 1)

c.setopt(pycurl.MAXREDIRS, 1)

c.setopt(pycurl.NOPROGRESS, 1)

c.setopt(pycurl.DNS_CACHE_TIMEOUT,30)

indexfile = open(os.path.dirname(os.path.realpath(__file__))+"/content.txt", "wb")

c.setopt(pycurl.WRITEHEADER, indexfile)

c.setopt(pycurl.WRITEDATA, indexfile)

try:

    c.perform()

except Exception,e:

    print "connecion error:"+str(e)

    indexfile.close()

    c.close()

    sys.exit()


NAMELOOKUP_TIME =  c.getinfo(c.NAMELOOKUP_TIME)

CONNECT_TIME =  c.getinfo(c.CONNECT_TIME)

PRETRANSFER_TIME =   c.getinfo(c.PRETRANSFER_TIME)

STARTTRANSFER_TIME = c.getinfo(c.STARTTRANSFER_TIME)

TOTAL_TIME = c.getinfo(c.TOTAL_TIME)

HTTP_CODE =  c.getinfo(c.HTTP_CODE)

SIZE_DOWNLOAD =  c.getinfo(c.SIZE_DOWNLOAD)

HEADER_SIZE = c.getinfo(c.HEADER_SIZE)

SPEED_DOWNLOAD=c.getinfo(c.SPEED_DOWNLOAD)


print "HTTP状态码:%s" %(HTTP_CODE)

print "DNS解析时间:%.2f ms"%(NAMELOOKUP_TIME*1000)

print "建立连接时间:%.2f ms" %(CONNECT_TIME*1000)

print "准备传输时间:%.2f ms" %(PRETRANSFER_TIME*1000)

print "传输开始时间:%.2f ms" %(STARTTRANSFER_TIME*1000)

print "传输结束总时间:%.2f ms" %(TOTAL_TIME*1000)

print "下载数据包大小:%d bytes/s" %(SIZE_DOWNLOAD)

print "HTTP头部大小:%d byte" %(HEADER_SIZE)

print "平均下载速度:%d bytes/s" %(SPEED_DOWNLOAD)


indexfile.close()

c.close()


print time.strftime( ISOTIMEFORMAT, time.gmtime( time.time() ) )

print "================================================================"

3.测试局域网内主机是否alive的小脚本

我们在对局域网的网络情况进行维护时,经常会遇到这样的问题,需要收集网络中存活的IP,这个时候可以写一个Python脚本,自动收集某一网段的IP。现在的IT技术型公司都比较大,网络工程师一般会规划几个VLAN(网段),我们可以用如下这个脚本来收集某个VLAN下存活的主机,(此脚本在CentOS 6.4 x86_64下已测试通过):

#!/usr/bin/python

import os

import re

import time

import sys

import subprocess


lifeline = re.compile(r"(\d) received")

report = ("No response","Partial Response","Alive")


print time.ctime()

for host in range(1,254):

   ip = "192.168.1."+str(host)

   pingaling = subprocess.Popen(["ping","-q", "-c 2", "-r", ip], shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE)

   print "Testing ",ip,

   while 1:

      pingaling.stdout.flush()

      line = pingaling.stdout.readline()

      if not line: break

      igot = re.findall(lifeline,line)

      if igot:

           print report[int(igot[0])]

print time.ctime()

Python对空格的要求是非常严谨的,请大家注意下这个问题。脚本虽然短小,但非常实用、精悍,可避免到Windows 下去下载局域网检测工具。平时我们在日常工作中也应该注意多收集、多写一些这样的脚本,以达到简化运维工作的目的。

2.6.3 监控类脚本

在生产环境下,服务器的稳定情况会直接影响公司的生意和信誉,可见其有多重要。所以,我们需要即时掌握服务器的状态,我们一般会在机房部署Nagios-Server作为监控程序,然后用Shell和Python根据业务需求开发监控插件,实时监控线上业务。

1. Nginx负载均衡服务器上监控Nginx进程的脚本

由于笔者公司电子商务业务网站前端的Load Balance用到了Nginx+Keepalived架构,而Keepalived无法进行Nginx服务的实时切换,所以设置了一个监控脚本nginx_pid.sh,每隔5秒钟就监控一次Nginx的运行状态(也可以由Superviored守护进程托管),如果发现有问题就关闭本机的Keepalived程序,让VIP切换到从Nginx负载均衡器上。在对线上环境进行操作的时候,人为地重启主Master的Nginx机器,从Nginx机器在很短的时间内就接管了VIP地址,即网站的实际内网地址(此内网地址可以通过防火墙映射为公网IP),进一步证实了此脚本的有效性,脚本内容如下(此脚本在CentOS 5.8/6.4 x86_64下均已测试通过):

#!/bin/bash

while :

do

    nginxpid=`ps -C nginx --no-header | wc -l`

    if [ $nginxpid -eq 0 ];then

        ulimit -SHn 65535

        /usr/local/nginx/sbin/nginx

        sleep 5

        if [ $nginxpid -eq 0 ];then

        /etc/init.d/keepalived stop

    fi

 fi

 sleep 5

done

2.系统文件打开数监测脚本

这个脚本比较方便,可用来查看Nginx进程下最大文件打开数,脚本代码如下(此脚本在CentOS 5.8/6.4 x86_64、Amazon Linux AMI x86_64下均已测试通过):

#!/bin/bash

for pid in `ps aux |grep nginx |grep -v grep|awk '{print $2}'`

do

cat /proc/${pid}/limits | grep 'Max open files'

done

脚本的运行结果如下所示:

Max open files            65535                65535                files     

Max open files            65535                65535                files     

Max open files            65535                65535                files     

Max open files            65535                65535                files     

Max open files            65535                65535                files     

3.监测MySQL主从复制是否同步

笔者有不少基于公网类型的网站(没有硬件防火墙,直接置于IDC机房)采用的都是MySQL主从架构,从机主要起备份数据库和冷备份的作用,虽然从机宕机了问题不大,但也会影响到数据的备份工作;这样的网站有数十个,如果逐个手动检查,每天都要花费不少时间,所以也用了脚本监控。

脚本设计思路:

1)此脚本应该能适应各种各样不同的内外网环境,即IP不同的环境。

2)让脚本顺便也监控下MySQL是否正常运行。

3)Slave机器的IO和SQL状态都必须为YES,缺一不可,这里用到了多重条件判断-a。

脚本内容如下所示(此脚本在CentOS 5.8 x86_64下已测试通过):

#!/bin/bash

#check MySQL_Slave Status

#crontab time 00:10

MYSQLPORT=`netstat -na|grep "LISTEN"|grep "3306"|awk -F[:" "]+ '{print $4}'`

MYSQLIP=`ifconfig eth0|grep "inet addr" | awk -F[:" "]+ '{print $4}'`

STATUS=$(/usr/local/webserver/mysql/bin/mysql -u yuhongchun -pyuhongchun101 -S /tmp/mysql.sock -e "show slave status\G" | grep -i "running")

IO_env=`echo  $STATUS | grep IO | awk  ' {print $2}'`

SQL_env=`echo $STATUS | grep SQL | awk  '{print $2}'`



if [ "$MYSQLPORT" == "3306" ]

then

    echo "mysql is running"

else

    mail -s "warn!server: $MYSQLIP mysql is down" yuhongchun027@163.com

fi


if [ "$IO_env" = "Yes" -a "$SQL_env" = "Yes" ]

then

    echo "Slave is running!"

else

    echo "#######  $date  #########">> /data/data/check_mysql_slave.log

    echo "Slave is not running!" >> /data/data/check_mysql_slave.log

    mail -s "warn! $MySQLIP_replicate_error" yuhongchun027@163.com << /data/data/check_mysql_slave.log

fi

建议每10分钟运行一次,脚本如下:

*/10 * * * * root /bin/sh /root/mysql_slave.sh 

要记得在每台MySQL从机上分配一个yuhongchun的用户,权限大些也没关系,只限定在本地运行,脚本如下所示:

grant all privileges on *.* to "yuhongchun"@"127.0.0.1" identified by "yuhongchun101";

grant all privileges on *.* to "yuhongchun"@"localhost" identified by "yuhongchun101";

4.监控Python程序是否正常运行

需求比较简单,主要是监控业务进程rsync_redis.py是否正常运行,有没有发生crash的情况。另外,建议将类似于rsync_redis.py的重要业务进程交由Superviored守护进程托管。此脚本内容如下所示(此脚本在Amazon Linux AMI x86_64下已测试通过):

#!/bin/bash

sync_redis_status=`ps aux | grep sync_redis.py | grep -v grep | wc -l `

if [ ${sync_redis_status} != 1 ]; then

    echo "Critical! sync_redis is Died"

    exit 2

else

    echo "OK! sync_redis is Alive"

    exit 0

fi

2.6.4 开发类脚本

业务需求在不断地变化,有时候互联网上的开源方案并不能全部解决,这个时候就需要自己写一些开发类的脚本来满足工作中的需求了,虽然很多时候脚本都可以独立运行,但笔者的做法还是尽量将其return结果写成Nagios能够识别的格式,以便配合Nagios发送报警邮件和信息。

1.监测redis是否正常运行

笔者接触的线上NoSQL业务主要是redis数据库,多用于处理大量数据的高访问负载需求。为了最大化地利用资源,每个redis实例分配的内存并不是很大,有时候程序组的同事导入数据量大的IP list时会导致redis实例崩溃,所以笔者开发了一个redis监测脚本并配合Nagios进行工作,脚本内容如下所示(此脚本在Amazon Linux AMI x86_64下已测试通过):

#!/usr/bin/python

#Check redis Nagios Plungin,Please install the redis-py module.

import redis

import sys 


STATUS_OK = 0

STATUS_WARNING = 1

STATUS_CRITICAL = 2


HOST = sys.argv[1]

PORT = int(sys.argv[2])

WARNING = float(sys.argv[3])

CRITICAL = float(sys.argv[4])


def connect_redis(host, port):

    r = redis.Redis(host, port, socket_timeout = 5, socket_connect_timeout = 5)

    return r


def main():

    r = connect_redis(HOST, PORT)

    try:

        r.ping()

    except:

        print HOST,PORT,'down'

        sys.exit(STATUS_CRITICAL)


    redis_info = r.info()

    used_mem = redis_info['used_memory']/1024/1024/1024.0

    used_mem_human = redis_info['used_memory_human']


    if WARNING <= used_mem < CRITICAL:

        print HOST,PORT,'use memory warning',used_mem_human

        sys.exit(STATUS_WARNING)

    elif used_mem >= CRITICAL:

        print HOST,PORT,'use memory critical',used_mem_human

        sys.exit(STATUS_CRITICAL)

    else:

        print HOST,PORT,'use memory ok',used_mem_human

        sys.exit(STATUS_OK)


if __name__ == '__main__':

    main()

2.监测机器的IP连接数

需求其实比较简单,先统计IP连接数,如果ip_conns值小于15 000则显示为正常,介于15 000至20 000之间为警告,如果超过20 000则报警,脚本内容如下所示(此脚本在Amazon Linux AMI x86_64下已测试通过):

#!/bin/bash

#Nagios plugin For ip connects

#$1 = 15000 $2 = 20000

ip_conns=`netstat -an | grep tcp | grep EST | wc -l`

messages=`netstat -ant | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'|tr -s '\n' ',' | sed -r 's/(.*),/\1\n/g' `


if [ $ip_conns -lt $1 ]

then

    echo "$messages,OK -connect counts is $ip_conns"

    exit 0

fi

if [ $ip_conns -gt $1 -a $ip_conns -lt $2 ]

then

    echo "$messages,Warning -connect counts is $ip_conns"

    exit 1

fi

if [ $ip_conns -gt $2 ]

then

    echo "$messages,Critical -connect counts is $ip_conns"

    exit 2

fi

3.监测机器的CPU利用率脚本

线上的bidder业务机器,在业务繁忙的高峰期会出现CPU利用率达到100%(sys%+ user%),导致后面的流量打在上面却完全进不去的情况,但此时机器、系统负载及Nginx+Lua进程都是完全正常的,所以这种情况下需要开发一个CPU利用率脚本,在超过自定义阀值时报警,方便运维人员批量添加bidder AMI机器以应对峰值,AWS EC2实例机器是可以以小时来计费的,大家在这里也要注意分清系统负载和CPU利用率之间的区别。脚本内容如下所示(此脚本在Amazon Linux AMI x86_64下已测试通过):

#!/bin/bash

# ==============================================================================

# CPU Utilization Statistics plugin for Nagios 

#

# USAGE     :   ./check_cpu_utili.sh [-w <user,system,iowait>] [-c <user,system,iowait>] ( [ -i <intervals in second> ] [ -n <report number> ])

#           

# Exemple: ./check_cpu_utili.sh 

#          ./check_cpu_utili.sh -w 70,40,30 -c 90,60,40

#          ./check_cpu_utili.sh -w 70,40,30 -c 90,60,40 -i 3 -n 5

#-------------------------------------------------------------------------------

# Paths to commands used in this script.  These may have to be modified to match your system setup.

IOSTAT="/usr/bin/iostat"


# Nagios return codes

STATE_OK=0

STATE_WARNING=1

STATE_CRITICAL=2

STATE_UNKNOWN=3


# Plugin parameters value if not define 

LIST_WARNING_THRESHOLD="70,40,30"

LIST_CRITICAL_THRESHOLD="90,60,40"

INTERVAL_SEC=1

NUM_REPORT=1

# Plugin variable description

PROGNAME=$(basename $0)


if [ ! -x $IOSTAT ]; then

    echo "UNKNOWN: iostat not found or is not executable by the nagios user."

    exit $STATE_UNKNOWN

fi


print_usage() {

        echo ""

        echo "$PROGNAME $RELEASE - CPU Utilization check script for Nagios"

        echo ""

        echo "Usage: check_cpu_utili.sh -w -c (-i -n)"

        echo ""

        echo "  -w  Warning threshold in % for warn_user,warn_system,warn_iowait CPU (default : 70,40,30)"

        echo "  Exit with WARNING status if cpu exceeds warn_n"

        echo "  -c  Critical threshold in % for crit_user,crit_system,crit_iowait CPU (default : 90,60,40)"

        echo "  Exit with CRITICAL status if cpu exceeds crit_n"

        echo "  -i  Interval in seconds for iostat (default : 1)"

        echo "  -n  Number report for iostat (default : 3)"

        echo "  -h  Show this page"

        echo ""

    echo "Usage: $PROGNAME"

    echo "Usage: $PROGNAME --help"

    echo ""

    exit 0

}


print_help() {

    print_usage

        echo ""

        echo "This plugin will check cpu utilization (user,system,CPU_Iowait in %)"

        echo ""

    exit 0

}


# Parse parameters

while [ $# -gt 0 ]; do

    case "$1" in

        -h | --help)

            print_help

            exit $STATE_OK

            ;;

        -v | --version)

                print_release

                exit $STATE_OK

                ;;

        -w | --warning)

                shift

                LIST_WARNING_THRESHOLD=$1

                ;;

        -c | --critical)

               shift

                LIST_CRITICAL_THRESHOLD=$1

                ;;

        -i | --interval)

               shift

               INTERVAL_SEC=$1

                ;;

        -n | --number)

               shift

               NUM_REPORT=$1

                ;;        

        *)  echo "Unknown argument: $1"

            print_usage

            exit $STATE_UNKNOWN

            ;;

        esac

shift

done


# List to Table for warning threshold (compatibility with 

TAB_WARNING_THRESHOLD=(`echo $LIST_WARNING_THRESHOLD | sed 's/,/ /g'`)

if [ "${#TAB_WARNING_THRESHOLD[@]}" -ne "3" ]; then

    echo "ERROR : Bad count parameter in Warning Threshold"

    exit $STATE_WARNING

else  

USER_WARNING_THRESHOLD=`echo ${TAB_WARNING_THRESHOLD[0]}`

SYSTEM_WARNING_THRESHOLD=`echo ${TAB_WARNING_THRESHOLD[1]}`

IOWAIT_WARNING_THRESHOLD=`echo ${TAB_WARNING_THRESHOLD[2]}` 

fi


# List to Table for critical threshold

TAB_CRITICAL_THRESHOLD=(`echo $LIST_CRITICAL_THRESHOLD | sed 's/,/ /g'`)

if [ "${#TAB_CRITICAL_THRESHOLD[@]}" -ne "3" ]; then

    echo "ERROR : Bad count parameter in CRITICAL Threshold"

    exit $STATE_WARNING

else

USER_CRITICAL_THRESHOLD=`echo ${TAB_CRITICAL_THRESHOLD[0]}`

SYSTEM_CRITICAL_THRESHOLD=`echo ${TAB_CRITICAL_THRESHOLD[1]}`

IOWAIT_CRITICAL_THRESHOLD=`echo ${TAB_CRITICAL_THRESHOLD[2]}`

fi


if [ ${TAB_WARNING_THRESHOLD[0]} -ge ${TAB_CRITICAL_THRESHOLD[0]} -o ${TAB_WARNING_THRESHOLD[1]} -ge ${TAB_CRITICAL_THRESHOLD[1]} -o ${TAB_WARNING_THRESHOLD[2]} -ge ${TAB_CRITICAL_THRESHOLD[2]} ]; then

  echo "ERROR : Critical CPU Threshold lower as Warning CPU Threshold "

  exit $STATE_WARNING

fi


CPU_REPORT=`iostat -c $INTERVAL_SEC $NUM_REPORT | sed -e 's/,/./g' | tr -s ' ' ';' | sed '/^$/d' | tail -1`

CPU_REPORT_SECTIONS=`echo ${CPU_REPORT} | grep ';' -o | wc -l`

CPU_USER=`echo $CPU_REPORT | cut -d ";" -f 2`

CPU_SYSTEM=`echo $CPU_REPORT | cut -d ";" -f 4`

CPU_IOWAIT=`echo $CPU_REPORT | cut -d ";" -f 5`

CPU_STEAL=`echo $CPU_REPORT | cut -d ";" -f 6`

CPU_IDLE=`echo $CPU_REPORT | cut -d ";" -f 7`

NAGIOS_STATUS="user=${CPU_USER}%,system=${CPU_SYSTEM}%,iowait=${CPU_IOWAIT}%,idle=${CPU_IDLE}%"

NAGIOS_DATA="CpuUser=${CPU_USER};${TAB_WARNING_THRESHOLD[0]};${TAB_CRITICAL_THRESHOLD[0]};0"


CPU_USER_MAJOR=`echo $CPU_USER| cut -d "." -f 1`

CPU_SYSTEM_MAJOR=`echo $CPU_SYSTEM | cut -d "." -f 1`

CPU_IOWAIT_MAJOR=`echo $CPU_IOWAIT | cut -d "." -f 1`

CPU_IDLE_MAJOR=`echo $CPU_IDLE | cut -d "." -f 1`


# Return

if [ ${CPU_USER_MAJOR} -ge $USER_CRITICAL_THRESHOLD ]; then

        echo "CPU STATISTICS OK:${NAGIOS_STATUS} | CPU_USER=${CPU_USER}%;70;90;0;100"

        exit $STATE_CRITICAL

    elif [ ${CPU_SYSTEM_MAJOR} -ge $SYSTEM_CRITICAL_THRESHOLD ]; then

        echo "CPU STATISTICS OK:${NAGIOS_STATUS} | CPU_USER=${CPU_USER}%;70;90;0;100"

        exit $STATE_CRITICAL

    elif [ ${CPU_IOWAIT_MAJOR} -ge $IOWAIT_CRITICAL_THRESHOLD ]; then

        echo "CPU STATISTICS OK:${NAGIOS_STATUS} | CPU_USER=${CPU_USER}%;70;90;0;100"

        exit $STATE_CRITICAL

    elif [ ${CPU_USER_MAJOR} -ge $USER_WARNING_THRESHOLD ] && [ ${CPU_USER_MAJOR} -lt $USER_CRITICAL_THRESHOLD ]; then

        echo "CPU STATISTICS OK:${NAGIOS_STATUS} | CPU_USER=${CPU_USER}%;70;90;0;100"

        exit $STATE_WARNING 

      elif [ ${CPU_SYSTEM_MAJOR} -ge $SYSTEM_WARNING_THRESHOLD ] && [ ${CPU_SYSTEM_MAJOR} -lt $SYSTEM_CRITICAL_THRESHOLD ]; then

        echo "CPU STATISTICS OK:${NAGIOS_STATUS} | CPU_USER=${CPU_USER}%;70;90;0;100"

        exit $STATE_WARNING 

      elif  [ ${CPU_IOWAIT_MAJOR} -ge $IOWAIT_WARNING_THRESHOLD ] && [ ${CPU_IOWAIT_MAJOR} -lt $IOWAIT_CRITICAL_THRESHOLD ]; then

        echo "CPU STATISTICS OK:${NAGIOS_STATUS} | CPU_USER=${CPU_USER}%;70;90;0;100"

        exit $STATE_WARNING

else

       

        echo "CPU STATISTICS OK:${NAGIOS_STATUS} | CPU_USER=${CPU_USER}%;70;90;0;100"

        exit $STATE_OK

fi

此脚本参考了Nagios的官方文档https://exchange.nagios.org/并进行了代码精简和移值,源代码是运行在ksh下面的,这里将其移植到了bash下面,ksh下定义数组的方式跟bash还是有区别的;另外有一点也请大家注意,Shell本身是不支持浮点运算的,但可以通过bc或awk的方式来处理。

另外,若要配合PNP4nagios出图(PNP4nagios可以观察一段周期内的CPU利用率峰值),此脚本还可以更精简,脚本内容如下所示(此脚本在Amazon Linux AMI x86_64下已测试通过):

#!/bin/bash

# Nagios return codes

STATE_OK=0

STATE_WARNING=1

STATE_CRITICAL=2

STATE_UNKNOWN=3


# Plugin parameters value if not define 

LIST_WARNING_THRESHOLD="90"

LIST_CRITICAL_THRESHOLD="95"

INTERVAL_SEC=1

NUM_REPORT=5


CPU_REPORT=`iostat -c $INTERVAL $NUM_REPORT  | sed -e 's/,/./g' | tr -s ' ' ';' | sed '/^$/d' |tail -1`

CPU_REPORT_SECTIONS=`echo ${CPU_REPORT} | grep ';' -o | wc -l`

CPU_USER=`echo $CPU_REPORT | cut -d ";" -f 2`

CPU_SYSTEM=`echo $CPU_REPORT | cut -d ";" -f 4`

# Add for integer shell issue

CPU_USER_MAJOR=`echo $CPU_USER | cut -d "." -f 1`

CPU_SYSTEM_MAJOR=`echo $CPU_SYSTEM | cut -d "." -f 1`

CPU_UTILI_COU=`echo ${CPU_USER} + ${CPU_SYSTEM}|bc`

CPU_UTILI_COUNTER=`echo $CPU_UTILI_COU | cut -d "." -f 1`


# Return

if [ ${CPU_UTILI_COUNTER} -lt ${LIST_WARNING_THRESHOLD} ]

then

    echo "OK - CPUCOU=${CPU_UTILI_COU}% | CPUCOU=${CPU_UTILI_COU}%;80;90"

    exit ${STATE_OK}

fi

if [ ${CPU_UTILI_COUNTER} -gt ${LIST_WARNING_THRESHOLD} -a ${CPU_UTILI_COUNTER} -lt ${LIST_CRITICAL_THRESHOLD} ]

then

    echo "Warning - CPUCOU=${CPU_UTILI_COUNTER}% | CPUCOU=${CPU_UTILI_COUNTER}%;80;90"

    exit ${STATE_WARNING}

fi

if [ ${CPU_UTILI_COUNTER} -gt ${LIST_CRITICAL_THRESHOLD} ]

then

   echo "Critical - CPUCOU=${CPU_UTILI_COUNTER}% | CPUCOU=${CPU_UTILI_COUNTER}%;80;90" 

    exit ${STATE_CRITICAL}

fi

2.6.5 自动化类脚本

1.批量生成账户脚本

在内网开发环境中,有时需要为开发组的同事批量生成账户,如果手动添加的话会非常麻烦,这时可以写一段Shell脚本来自动完成这项工作。在首次登录时密码均是统一的,在移交给开发人员使用时让他们自行更改即可,脚本代码如下(此脚本在CentOS 5.8 / 6.4 x86_64下均已测试通过):

#!/bin/bash

#此脚本应用于开发环境下批量生成用户

for name in tom jerry joe jane yhc brain

do

      useradd $name

      echo redhat | passwd --stdin $name

don

passwd --stdin这行代码的的作用是将前面的输入通过管道命令作为自己的输出,从而避免脚本交互,达到自动化的目的。

笔者个人觉得用脚本的方式来批量自动添加用户的方法较之Ansible的user模块更为简便,有兴趣的朋友也可以研究比较下。

2.系统初始化脚本

此脚本用于新装Linux的相关配置工作,比如禁用iptables、SElinux及ipv6,优化系统内核,停掉一些没必要启动的系统服务等。此脚本可用于公司内部的开发机器的批量部署,脚本代码如下所示(此脚本在CentOS 6.4 x86_64下已测试通过):

#!/bin/bash

#添加epel外部yum扩展源

cd /usr/local/src

wget http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm

rpm -ivh epel-release-6-8.noarch.rpm

#安装gcc基础库文件及sysstat工具

yum -y install gcc gcc-c++ vim-enhanced unzip unrar sysstat

#配置ntpdate自动对时

yum -y install ntp

echo "01 01 * * * /usr/sbin/ntpdate ntp.api.bz    >> /dev/null 2>&1" >> /etc/crontab

ntpdate ntp.api.bz

service crond restart

#配置文件的ulimit值

ulimit -SHn 65534

echo "ulimit -SHn 65534" >> /etc/rc.local

cat >> /etc/security/limits.conf << EOF

*                     soft     nofile             65534

*                     hard     nofile             65534

EOF


#基础系统内核优化

cat >> /etc/sysctl.conf << EOF

net.ipv4.tcp_syncookies = 1

net.ipv4.tcp_syn_retries = 1

net.ipv4.tcp_tw_recycle = 1

net.ipv4.tcp_tw_reuse = 1

net.ipv4.tcp_fin_timeout = 1

net.ipv4.tcp_keepalive_time = 1200

net.ipv4.ip_local_port_range = 1024 65535

net.ipv4.tcp_max_syn_backlog = 16384

net.ipv4.tcp_max_tw_buckets = 36000

net.ipv4.route.gc_timeout = 100

net.ipv4.tcp_syn_retries = 1

net.ipv4.tcp_synack_retries = 1

net.core.somaxconn = 16384

net.core.netdev_max_backlog = 16384

net.ipv4.tcp_max_orphans = 16384

EOF

/sbin/sysctl -p


#禁用control-alt-delete组合键以防止误操作

sed -i 's@ca::ctrlaltdel:/sbin/shutdown -t3 -r now@#ca::ctrlaltdel:/sbin/shutdown -t3 -r now@' /etc/inittab

#关闭SElinux

sed -i 's@SELINUX=enforcing@SELINUX=disabled@' /etc/selinux/config

#关闭iptables

service iptables stop

chkconfig iptables off

#ssh服务配置优化,请保持机器中至少有一个具有sudo权限的用户,下面的配置会禁止root远程登录

sed -i 's@#PermitRootLogin yes@PermitRootLogin no@' /etc/ssh/sshd_config #禁止 root远程登录

sed -i 's@#PermitEmptyPasswords no@PermitEmptyPasswords no@' /etc/ssh/sshd_config #禁止空密码登录

sed -i 's@#UseDNS yes@UseDNS no@' /etc/ssh/sshd_config /etc/ssh/sshd_config

service sshd restart

#禁用ipv6地址

echo "alias net-pf-10 off" >> /etc/modprobe.d/dist.conf

echo "alias ipv6 off" >> /etc/modprobe.d/dist.conf

chkconfig ip6tables off

#vim基础语法优化

echo "syntax on" >> /root/.vimrc

echo "set nohlsearch" >> /root/.vimrc

#精简开机自启动服务,安装最小化服务的机器初始可以只保留crond、network、rsyslog、sshd这4个服务。

for i in `chkconfig --list|grep 3:on|awk '{print $1}'`;do chkconfig --level 3 $i off;done

for CURSRV  in crond rsyslog sshd network;do chkconfig --level 3 $CURSRV on;done

#重启服务器

reboot

2.7 小结

本章向大家详细说明了Shell的基础语法,以及sed和awk在日常工作中的使用案例,并用Shell命令grep和find结合正则表达式演示了正则表达式的一些基础用法。在后面的实例中,又根据备份类、监控类、统计类、自动化运维类、运维开发类向大家演示了在生产环境下我们经常用到的Shell和Python脚本。我们在感叹Shell脚本强大的管理功能的同时,也应该比较清楚Shell脚本在开发功能上的不足,而Python正好能够弥补这个缺点,它继承了传统编译语言的强大性和通用性,同时也借鉴了简单脚本和解释语言的易用性,运行速度也不慢,适合网站开发,正好可以弥补Shell脚本的不足。结合这两种脚本语言, 我们的系统运维工作和DevOps工作会更加得心应手。






第3章

轻量级自动化运维工具Fabric详解

近期公司的业务系统代码发布频繁,笔者同时在几个项目组里面穿插工作,发现发布和运维的工作都相当机械,加上频率比较高,导致时间的浪费也比较多。很多测试工作,例如通过SSH登录到测试环境,推送代码,然后修改Bug进行测试,这些操作都是非常机械并且具有重复性的。更让人郁闷的是,每次的操作都是相同的,命令基本上都是一样的,并且是在多台机器上执行,很难在本机上以一个脚本来搞定,主要时间都浪费在使用SSH登录和输入命令上了。这个时候需要一个轻量级的自动化运维工具,来帮助我们解决这些问题,Fabric就顺应这个需求而出现了,它非常适合于这些简单的、重复性的远程操作。Fabirc是基于Python语言开发的,前文2.1节就提到过,Python应用非常火爆,接下来看看Python的应用领域及其流行的原因。

3.1 Python语言的应用领域

1.云计算基础设施

云计算平台分为私有云和公有云。私有云平台如大名鼎鼎的OpenStack,就是以Python语言编写的。公有云,无论是AWS、Azure、GCE(Google Compute Engine)、阿里云还是青云,都提供了Python SDK,其中GCE只提供了Python和JavaScript的SDK,青云只提供了Python SDK。由此可见各家云平台对Python的重视。

软件开发工具包(Software Development Kit,SDK)一般是一些开发工具的集合,用于为特定的软件包、软件框架、硬件平台、操作系统等创建应用软件。

2. DevOps

DevOps,中文名译作开发型运维。在互联网时代,只有能够快速试验新想法,并在第一时间,安全、可靠地交付业务价值,才能保持竞争力。DevOps推崇的自动化构建、测试、部署及系统度量等技术实践,在互联网时代是尤其重要的。

自动化构建是因应用而异的,如果是Python应用,因为有setuptools、pip、virtualenv、tox、flake8等工具的存在,所以自动化构建非常简单。而且,因为几乎所有的Linux版本都内置了Python解释器,所以用Python做自动化,系统不需要预安装什么软件。

自动化测试方面,目前流行的自动化测试框架有Robot Framework、Cucumber、Lettuce三种。基于Python的Robot Framework是企业级应用最喜欢的自动化测试框架,而且和语言无关。Cucumber也有很多支持者。基于Python的Lettuce可以实现完全一样的功能。此外,Locust(一个基于Python开发的开源负载测试工具)也开始在自动化性能测试方面受到越来越多的关注。

自动化配置管理工具,老牌的如Chef和Puppet,是基于Ruby语言开发设计的,目前仍保持着强劲的势头。不过,新生代Ansible、SaltStack,以及轻量级的自动化运维工具Fabric,均为Python语言开发。由于它们较前两者的设计更为轻量化,因此受到越来越多开发者的欢迎,并且已经给Chef和Puppet制造了不少的竞争压力。

3.网络爬虫

大数据的数据从哪里来?除了部分企业有能力自己产生大量的数据,大部分时候,是需要依靠网络爬虫来抓取互联网数据进行分析的。

网络爬虫是Python的传统强势领域,最流行的爬虫框架Scrapy、HTTP工具包urlib2、HTML解析工具Beautiful Soup、XML解析器lxml等,都是能够独当一面的类库。笔者公司的分布式网络爬虫程序也是基于Scrapy开发的。不过,网络爬虫并不仅仅是打开网页,解析HTML这么简单。高效的爬虫要能够支持大量灵活的并发操作,常常要能够同时抓取几千甚至上万个网页,使用传统的线程池方式资源浪费比较大,线程数上千之后系统资源基本上就全浪费在线程调度上了。由于Python能够很好地支持协程(Coroutine)操作,因此基于Python发展了很多并发库,如Gevent、Eventlet,还有Celery之类的分布式任务框架等。被认为是比AMQP更高效的ZeroMQ最早提供的也是Python版本。有了对高并发的支持,网络爬虫才可以真正达到大数据规模。

4.数据处理

从统计理论,到数据挖掘、机器学习,再到最近几年提出来的深度学习理论,数据科学正处于百花齐放的时代。数据科学家们都用什么语言编程呢?Python是数据科学家最喜欢的语言之一。和R语言不同,Python本身就是一门工程性语言,数据科学家用Python实现的算法,可以直接用在产品中,这对于初创的大数据公司来说,是非常有利于节省成本的。正是基于数据科学家对Python和R的热爱,Spark为了“讨好”数据科学家,对这两种语言都提供了非常好的支持。

Python的数据处理相关类库非常多。比如,高性能的科学计算类库NumPy和SciPy,给其他高级算法打下了非常好的基础;Matplotlib让Python画图变得像Matlab一样简单;Scikit-learn和Milk实现了很多机器学习算法,基于这两个库实现的Pylearn2,是深度学习领域的重要成员;Theano利用GPU加速,实现了高性能数学符号计算和多维矩阵计算。当然,还有Pandas,一个在工程领域已被广泛使用的大数据处理类库,其DataFrame的设计借鉴自R语言,后来又启发Spark项目实现了类似机制。

除了这些领域以外,Python还被广泛应用于Web开发、游戏开发、手机开发、数据库开发等众多领域。

3.2 选择Python的原因

对于开发工程师而言,Python的优雅和简洁无疑具有最大的吸引力,在Python交互式环境中,执行import this命令,读一读Python之禅,你就会明白Python为什么如此吸引人了。Python社区一直非常有活力,和NodeJS社区软件包的爆炸式增长不同,Python的软件包增长速度一直比较稳定,同时软件包的质量也相对较高。有很多人诟病Python对于空格的要求过于苛刻,但正是基于这个严格的要求,才使得Python在做大型项目时比其他语言更有优势。OpenStack项目的代码总共超过200万行,也证明了这一点。

对于运维工程师而言,Python的最大优势在于,几乎所有的Linux发行版本都内置了Python解释器。Shell虽然功能强大,但缺点很多:语法不够优雅,不支持面向对象、没有第三方库支持,所以在写比较复杂的任务时会很痛苦。用Python替代Shell,完成一些Shell实现不了的复杂任务,对于运维人员、运维工程师来说,是一次解放。

对于DevOps而言,Python的优势在于它是一门强大的“胶水语言”,特别适合应用于Web后端、服务器开发,其优点如下:

Python的代码风格简洁易懂、易于维护,包括语法优势不用写大括号,代码注释风格统一,强调做一个事情只有一种方法等。

有着丰富的Web开源框架,主流的包括Web2py、Web.py、Zope2、Pyramid、Django等。

具有跨平台能力,支持Mac、Linux、Windows等系统。

Python可用库和模块比较多,非常方便。

Python社区非常活跃,在其社区里基本上能够找到一切你所需要的答案。

基于以上原因,我们还有什么理由不选择Python呢?

3.3 Python的版本说明

关于Python的版本需要重点说明下,Python的2.x版本和3.x版本的差异还是很大的,语法上也有很多是完全不一样的,这里以线上环境说明。在线上环境中,暂时还是只用Python 2.7版本,具体原因如下: 

由于历史原因,笔者公司业务系统的Python代码是基于Python 2.7版本开发的,如果向Python3.x版本移植的话工作量太大,而且不能保证系统的稳定性,所以暂时不考虑采用Python3.x版本。

现在采用的很多第三方类库都只提供了Python2.x版本,而没有提供Python3.x版本。

开发环境为了跟线上环境保持一致,也主要是Python 2.7版本。

基于上面的原因,本章的内容也以Python 2.7版本为主,下面所有有关Python的代码都是基于Python 2.7版本的,并没有涉及Python 3.x版本,这一点希望大家注意。

3.4 增强的交互式环境IPython

虽然Python自带了原生的Python Shell,但功能上还是比IPython略逊一筹。IPython是一种基于Python的交互式解释器。相较于原生的Python Shell,IPython提供了更为强大的编辑和交互功能。IPython拥有一套复杂的并行和分配计算结构,使得各种并行应用能够交互式地被开发、执行、调试和监控。事实上,IPython中的“I”就代表“交互”。这个解释器的强大使其不仅可以作为Python的解释器,甚至还可以直接作为系统管理员的工作环境。IPython具有以下特征。

Tab补全:可以有效地补齐Python语言的模块、方法和类等(原生的Python Shell不支持Tab补全功能)。

magic函数:内置了很多函数用来实现各种特征。

宏:可以将一段代码定义为一个宏,以便日后运行。

历史记录:提供了强大的历史记录功能。

执行系统命令:可以直接在交互式Shell中执行系统命令。

社区支持:IPython有着非常活跃的社区支持。

下面介绍IPython的安装过程。

IPython的主页是http:/ipython.scipy.org/,其中有关于IPython的官方资源,包括文档、下载和常见问题等。在CentOS 6.4 x86_64上通过yum安装IPython是非常简单的,步骤如下:

1)下载epel并安装epel源,命令如下所示:

wget http://ftp.linux.ncsu.edu/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm

rpm -ivh epel-release-6-8.noarch.rpm

2)通过yum安装IPython,命令如下所示:

yum -y install ipython

安装成功以后,就可以直接输入命令ipython来启动IPython解释器了,大家可以对比一下,IPython界面比原生的Python Shell界面更漂亮,如图3-1所示。


图3-1 IPython安装成功界面

3.5 Python(x,y)介绍

Python(x,y)是Windows下一个免费的科学和工程开发包,提供数学计算、数据分析和可视化展示。从名字就能看出来这个发行版附带了科学计算方面的很多常用库,另外还有用于桌面软件界面制作的PyQt,以及进行文档处理、生成EXE文件等的常用库。此外,它还包含了大量的工具,如IDE、制图制表的工具、加强的互动Shell等。下文提到的很多软件在此发行版中都有附带。在其他方面,Python(x, y)还附带了手工整理出的所有库的离线文档,每个小版本升级都提供了单独的补丁。

Python(x,y)安装成功以后,就默认自带了IPthon、PyDev(Python IDE)这些软件包,非常方便,推荐大家在Windows下安装此软件包来学习Python的基础语法。Python(x,y)里面包含的软件如图3-2所示。


图3-2 Python(x,y)包含的软件图示

3.6 轻量级自动化运维工具Fabric介绍

笔者公司目前的数据中心采用的是分布式部署方案,在全球多地都有数据中心。数据中心采用的是AWS EC2机器,在核心的数据中心里,EC2机器的数量比较多,基本上每个数据中心都在运行着几百台AWS EC2机器,而且业务繁忙的时候,会通过AWS AMI(Amazon系统映像)直接上线几十台相同业务的EC2机器,它们的机器类型、系统应用和配置文件基本上都是一模一样的,很多时候需要修改相同的配置文件和执行相同的操作,这个时候为了避免重复性的劳动就需要用到自动化运维工具,轻量级自动化运维工具Fabric在这里是首选。Fabric是基于Python语言开发的,是开发组同事的最爱。为了方便自动化运维,我们在每个数据中心都部署了跳板机(在跳板机上部署了Fabric),其物理拓扑图如图3-3所示。


图3-3 跳板机物理拓扑图

部署跳板机的好处有以下几点:

基于安全性考虑,只有跳板机上开放了公网IP和SSH Key登录,其他的业务机器默认只允许内网登录,公网IP地址不对外开放。

为了方便自动化运维部署,跳板机上做了免密码登录,可以直接通过SSH命令操作其他业务机器。

设置了权限控制管理,跳板机上部署了几套Key,分别对应于不同的权限分配,公司的同事可按照不同的职能获得相应的私钥登录跳板机,不同的职能分配的相应权限也是不一样的。

Fabric是基于Python(2.5及以上版本)实现的SSH命令行工具,简化了SSH的应用程序部署及系统管理任务,它为系统提供了基础的操作组件,可以实现本地或远程Shell命令,包括文件上传、下载、脚本执行及完整执行日志输出等功能。Fabric的官方地址为http://www.fabfile.org,目前最高版本为1.30。

Amazon Linux AMI是由Amazon Web Services提供的受支持和维护的Linux映像,用于Amazon Elastic Compute Cloud(Amazon EC2)。旨在为Amazon EC2上运行的应用程序提供稳定、安全和高性能的执行环境。它支持最新的EC2实例类型功能,并包括可与AWS轻松集成的软件包。Amazon Web Services为运行Amazon Linux AMI的所有实例提供了持续的安全性和维护更新。Amazon Linux AMI对于Amazon EC2用户是免费的。

3.6.1 Fabric的安装

安装Fabric时,可以选择采用Python的pip、easy_install及源码安装方式,这些方式能够很方便地解决包依赖关系。大家可以根据系统环境自行选择最优的安装方法,如果选择pip或easy_install安装方式,则其安装命令如下(如果系统是最小化安装,记得先提前安装好gcc、gcc-c++、make这些基础开发包和python-pip):

yum -y install make gcc gcc++ python-devel python-pip

pip是安装Python包的工具,提供了安装包、列出已经安装的包、升级包及卸载包的功能,可以通过pip工具直接安装Fabric,命令如下:

pip install fabric

这里推荐源码安装,安装步骤如下所示:

yum -y install python-setuptools

cd /usr/local/src

wget https://pypi.python.org/packages/source/F/Fabric/Fabric-1.3.0.tar.gz --no-check-certificate 

tar xvf Fabric-1.3.0.tar.gz

cd Fabric-1.3.0

python setup.py install

安装结果如下,如果出现以下信息则表示Fabric已经成功安装:

Downloading https://pypi.python.org/packages/source/s/ssh/ssh-1.8.0.tar.gz#md5=bc4dd59ec0c7bdf78a3840652cac824e

Processing ssh-1.8.0.tar.gz

Running ssh-1.8.0/setup.py -q bdist_egg --dist-dir /tmp/easy_install-Cw9PkJ/ssh-1.8.0/egg-dist-tmp-dkm93k

zip_safe flag not set; analyzing archive contents...

Adding ssh 1.8.0 to easy-install.pth file

Installed /usr/lib/python2.6/site-packages/ssh-1.8.0-py2.6.egg

Searching for pycrypto==2.6.1

Best match: pycrypto 2.6.1

Adding pycrypto 2.6.1 to easy-install.pth file


Using /usr/lib64/python2.6/site-packages

Finished processing dependencies for Fabric==1.3.0

下面检查下Fabric模块是否正常安装成功了,如果输入import fabric没有任何错误提示则表示已经成功安装,命令如下所示:

Python 2.6.6 (r266:84292, Jul 23 2015, 15:22:56) 

[GCC 4.4.7 20120313 (Red Hat 4.4.7-11)] on linux2

Type "help", "copyright", "credits" or "license" for more information.

>>> import fabric

>>>

3.6.2 命令行入口fab命令详解

fab作为Fabric的命令行入口,提供了丰富的参数调用,命令格式如下:

fab [options] -- [shell command]

其中,

-l:显示定义好的任务函数名。

-f:指定fab入口文件,默认入口文件名为fabfile.py,如果当前目录不存在fabfile.py,则必须用-f参数指定一个新的文件,不然会报错。

-g:指定网关设备,比如跳板机环境,填写跳板机IP即可。

-H:指定目标主机,多台主机用“,”号分隔。

-P:以异步并行方式运行多个主机任务,默认为串行运行。

-R:指定角色(role),以角色名区分不同的业务组设备。

-t:设置设备连接超时时间。

-T:设置远程主机命令执行超时时间。

-w:当命令执行失败,发出警告,而非默认终止任务。

如果想要通过Fabric得知远程机器192.168.1.205的hostname名,可执行如下命令:

fab -p redhat(root密码) -H 192.168.1.205  -- 'hostname'

记得在当前目录下用touch命令建立一个新的fabfile.py文件,不然会产生如下报错:

Traceback (most recent call last):

  File "/usr/lib/python2.6/site-packages/Fabric-1.3.0-py2.6.egg/fabric/main.py", line 600, in main

    arguments, remainder_arguments, default)

UnboundLocalError: local variable 'default' referenced before assignment

成功执行完fab命令以后,就可以看得到以下结果了:

[192.168.1.205] Executing task '<remainder>'

[192.168.1.205] run: uname -r

[192.168.1.205] out: 2.6.32-358.el6.x86_64

Done.

Disconnecting from 192.168.1.205... done.

3.6.3 Fabric的核心API

Fabric的核心API主要有7类:带颜色的输出类(color output)、上下文管理类(context managers)、装饰器类(decorators)、网络类(network)、操作类(operations)、任务类(tasks)、工具类(utils)。

Fabric提供了一组操作简单但功能强大的fabric.api命令集,简单地调用这些API就能完成大部分应用场景的需求,Fabric支持的常用命令及说明如下。

local:执行本地命令,如local ('uname -s')。

lcd:切换本地目录,如lcd ('/home')。

cd:切换远程目录,如cd ('/data/logs/')。

run:执行远程命令,如run ('free -m')。

sudo:以sudo方式执行远程命令,如sudo ('/etc/init.d/httpd start')。

put:上传本地文件到远程主机,如put ('/home/user.info','/data/user.info')。

get:从远程主机下载文件到本地,如get ('/home/user.info','/data/user.info')。

prompt:获得用户输入信息,如prompt ('please input user password:')。

confirm:获得提示信息确认,如confirm ('Test failed,Continue[Y/N]')。

reboot:重启远程主机,如reboot ()。

@task:函数修饰符。新版本的Fabric对面向对象的特性和命名空间有很好的支持。面向对象的继承和多态特性,对代码的复用极其重要。新版本的Fabric定义了常规的模块级别的函数,并带有装饰器@task,这会直接将该函数转化为task子类。该函数名会被作为任务名,后面会举例说明@task的用法。

@runs_once:函数修饰符。标识此修饰符的函数只会执行一次,不受多台主机影响。

下面来看看@task的用法,它可以为任务添加别名,命令如下:

from fabric.api import task

@task(alias='dwm')

def deploy_with_migrations():

    pass

用fab命令打印指定文件中存在的命令,如下:

fab -f /home/yhc/test.py --list

命令显示结果如下所示:

Available commands:

deploy_with_migrations

    dwm

还可以通过@task来设置默认的任务,比如deploy(部署)一个子模块,命令如下:

from fabric.api import task

@task

def migrate():

    pass

@task

def push()

    pass

@task

def provision():

    pass


@task(default=True)

def full_deploy():

    provision()

    push()

    migrate()


fab -f /home/yhc/test.py --list

结果如下所示:

Available commands:

    deploy

    deploy.full_deploy

    deploy.migrate

    deploy.provision

    deploy.push

也可以通过@task以类的形式定义任务,例如:

from fabric.api import task

from fabric.tasks import Task

class MyTask(Task):

    name = "deploy"

    def run(self, environment, domain="whatever.com"):

        run("git clone foo")

        sudo("service apache2 restart")

instance = MyTask()

下面采用@task方式的代码跟上面的代码效果是一样的:

from fabric.api import task

from fabric.tasks import Task

@task

def deploy(environment, domain="whatever.com"):

    run("git clone foo")

    sudo("service apache2 restart")

大家可以对比看看,是不是采用@task函数修饰器的方式更为简洁和直观呢?

关于@task修饰器的用法和其他fabric.api命令,请参考Fabric官方文档http://fabric-chs.readthedocs.org/zh_CN/chs/tutorial.html。

这里举个例子说明一下@runs_once用法,源码文件/home/yhc/test.py文件内容如下所示:

#!/usr/bin/python

# -*- coding: utf-8 -*-

from fabric.api import *

from fabric.colors import *


env.user = "root" #定义用户名,env对象的作用是定义Fabric指定文件的全局设定

env.password = "redhat" #定义密码

env.hosts = ['192.168.1.204','192.168.1.205']

#定义目标主机


@runs_once

#当有多台主机时只执行一次

def local_task(): #本地任务函数

    local("hostname")

    print red("hello,world")

    #打印红色字体的结果

def remote_task(): #远程任务函数

    with cd("/usr/local/src"):

       run("ls -lF | grep /$")

#with是Python中更优雅的语法,可以很好地处理上下文环境产生的异常,这里用了with以后相当于实现了"cd /var/www/html && ls -lsart"的效果。

通过fab命令调用local_task本地任务函数,命令如下:

fab -f test.py local_task

结果如下所示:

[192.168.1.204] Executing task 'local_task'

[localhost] local: hostname

client.cn7788.com

My hostname is client.cn7788.com

Hello,world!

Done.

上述命令显示的虽然不是本机的IP地址,但实际上并没有在主机192.168.1.204上面执行命令,而是在本地主机client.cn7788.com(IP为192.168.1.206的机器)上执行了命令,并以红色字体显示了 “hello,world”和“My hostname is client.cn7788.com”。

调用remote_task远程函数显示结果,分别在204和205的机器上打印/usr/local/src/下面存在的目录,结果如下:

[192.168.1.204] Executing task 'remote_task'

[192.168.1.204] run: ls -lF | grep /$

[192.168.1.204] out: drwxr-xr-x. 2 root root    4096 Nov 22 00:01 download/

[192.168.1.204] out: drwxr-xr-x. 9  501 games   4096 Nov 19 04:44 Fabric-1.3.0/

[192.168.1.204] out: drwxr-xr-x. 2 root root    4096 Nov 22 00:01 object/


[192.168.1.205] Executing task 'remote_task'

[192.168.1.205] run: ls -lF | grep /$

[192.168.1.205] out: drwxr-xr-x. 2 root root  4096 Nov 22 04:58 mysql/

[192.168.1.205] out: drwxr-xr-x. 2 root root  4096 Nov 22 04:58 puppet/

[192.168.1.205] out: drwxr-xr-x. 2 root root  4096 Nov 22 04:58 soft/

[192.168.1.205] out: drwxr-xr-x. 2 root root  4096 Nov  3 07:56 test/

Done.

Disconnecting from 192.168.1.204... done.

Disconnecting from 192.168.1.205... done.

3.7 Fabric应用实例

3.7.1 开发环境中的Fabric应用实例

笔者公司在开发环境下使用的都是Xen和KVM虚拟机器,有不少数据,因为是内网环境,所以直接用root和SSH密码连接。系统统一为CentOS 6.4 x86_64,内核版本为2.6.32-358.el6.x86_64,Python版本为2.6.6。

实例1,同步Fabric跳板机的/etc/hosts文件,脚本如下:

#!/usr/bin/python

# -*- coding: utf-8 -*-

from fabric.api import *

from fabric.colors import * 

from fabric.context_managers import *

#fabric.context_managers是Fabric的上下文管理类,这里需要import是因为下面会用到with


env.user = 'root'

env.hosts = ['192.168.1.200','192.168.1.205','192.168.1.206']

env.password = 'bilin101'


@task

#限定只有put_hosts_file函数对fab命令可见

def put_hosts_files():

   print yellow("rsync /etc/host File")

   with settings(warn_only=True): #出现异常时继续执行,不终止

       put("/etc/hosts","/etc/hosts")

       print green("rsync file success!")

'''这里用到with是确保即便发生异常,也将尽早执行下面的清理操作,一般来说,Python中的with语句一般多用于执行清理操作(如关闭文件),因为Python中打开文件以后的时间是不确定的,如果有其他程序试图访问打开的文件会导致出现问题。

'''

for host in env.hosts:

    env.host_string = host

    put_hosts_files()

实例2,同步公司内部开发服务器的git代码,现在互联网公司的开发团队应该都比较倾向于采用git作为开发版本管理工具了,此脚本稍微改动下应该也可以应用于线上的机器,脚本如下:

#!/usr/bin/python

# -*- coding: utf-8 -*-

from fabric.api import *

from fabric.colors import * 

from fabric.context_managers import *


env.user = 'root'

env.hosts = ['192.168.1.200','192.168.1.205','192.168.1.206']

env.password = 'redhat'



@task

#同上面一样,指定git_update函数只对fab命令可见

def git_update():

    with settings(warn_only=True):

        with cd('/home/project/github'):

            sudo('git stash clear')

            #清理当前git中所有的储藏,以便于我们stashing最新的工作代码

            sudo('git stash')

            '''如果想切换分支,但是又不想提交正在进行的工作,那么就得储藏这些变更。为了往git堆栈推送一个新的储藏,只需要运行git stash命令即可

            '''

            sudo('git pull')

            sudo('git stash apply')

            #完成当前代码pull以后,取回最新的stashing工作代码,这里使用命令git stash apply

            sudo('nginx -s reload')


for host in env.hosts:

    env.host_string = host

    git_update()

3.7.2 线上环境中的Fabric应用实例

笔者线上的核心业务机器统一都是AWS EC2主机,机器数量较多,每个数据中心都部署了Fabric跳板机(物理拓扑图可参考图3-3),系统为Amazon Linux,内核版本为3.14.34-27.48.amzn1.x86_64,Python版本为Python 2.6.9。

如果公司项目组核心开发人员离职,线上机器就都要更改密钥,由于密钥一般是以组的形式存在的,再加上机器数量繁多,因此单纯通过技术人员手工操作,基本上是一项不可能完成的任务,但若是通过Fabric自动化运维工具的话,这就是一项简单的工作了,由于现在的线上服务器多采用SSH Key的方式管理,所以对于大多数系统运维人员来说SSH Key分发也是工作内容之一,故而建议大家掌握此脚本的用法。示例脚本内容如下:

#!/usr/bin/python2.6

# -*- coding: utf-8 -*-

from fabric.api import *

from fabric.colors import * 

from fabric.context_managers import *

#这里为了简化工作,脚本采用纯Python的写法,没有采用Fabric的@task修饰器


env.user = 'ec2-user'

env.key_filename = '/home/ec2-user/.ssh/id_rsa'

hosts=['budget','adserver','bidder1','bidder2','bidder3','bidder4','bidder5','bidder6','bidder7','bidder8','bidder9',redis1','redis2','redis3','redis4','redis5','redis6']

#机器数量众多,这里只罗列了部分


def put_ec2_key():

    with settings(warn_only=False):

        put("/home/ec2-user/admin-master.pub","/home/ec2-user/admin-master.pub")

        sudo("\cp /home/ec2-user/admin-master.pub /home/ec2-user/.ssh/authorized_keys")

        #\cp的作用是取消其别名作用,即不让cp-i生效

        sudo("chmod 600 /home/ec2-user/.ssh/authorized_keys")


def put_admin_key():

    with settings(warn_only=False):

       put("/home/ec2-user/admin-operation.pub",

"/home/ec2-user/admin-operation.pub")

       sudo("\cp /home/ec2-user/admin-operation.pub  /home/admin/.ssh/authorized_keys")

       sudo("chown admin:admin /home/admin/.ssh/authorized_keys")

       sudo("chmod 600 /home/admin/.ssh/authorized_keys")


def put_readonly_key():

      with settings(warn_only=False):

      put("/home/ec2-user/admin-readonly.pub",

"/home/ec2-user/admin-readonly.pub")

      sudo("\cp /home/ec2-user/admin-readonly.pub /home/readonly/.ssh/authorized_keys")

      sudo("chown readonly:readonly /home/readonly/.ssh/authorized_keys")

      sudo("chmod 600 /home/readonly/.ssh/authorized_keys")


for host in hosts:

    env.host_string = host

    put_ec2_key()

    put_admin_key()

    put_readonly_key()

大家可以输入如下命令查看系统中定义的别名(CentOS 6.4 x86_64)。

alias

命令显示结果如下所示:

alias cp='cp -i'

alias l.='ls -d .* --color=auto'

alias ll='ls -l --color=auto'

alias ls='ls --color=auto'

alias mv='mv -i'

alias rm='rm -i'

alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'

Amazon Linux系统与CentOS 6.4略有差别,已经取消了cp的别名定义。

如果线上的Nagios 客户端的监控脚本因为业务需求又发生了改动,而bidder业务集群约有23台(下面只列出了其中10台),且其中的一个业务需求脚本前前后后改动了4次,这时,手动操作肯定会耗费大量人力及时间成本,因此这里用Fabric推送此脚本并执行,代码如下:

#!/usr/bin/python2.6

## -*- coding: utf-8 -*-

from fabric.api import *

from fabric.colors import *

from fabric.context_managers import *


user = 'ec2-user'

hosts=['bidder1','bidder2','bidder3','bidder4','bidder5','bidder6','bidder7','bidder8','bidder9','bidder10']

#机器数量比较多,这里只列出其中10台


@task

#这里用到了@task修饰器

def put_task():

    print yellow("Put Local File to Nagios Client")

    with settings(warn_only=True):

        put("/home/ec2-user/check_cpu_utili.sh",

"/home/ec2-user/check_cpu_utili.sh")

        sudo("cp /home/ec2-user/check_cpu_utili.sh /usr/local/nagios/libexec")

        sudo("chown nagios:nagios /usr/local/nagios/libexec/check_cpu_utili.sh")

        sudo("chmod +x /usr/local/nagios/libexec/check_cpu_utili")

        sudo("kill  `ps aux | grep nrpe | head -n1 | awk '{print $2}' `")

        sudo("/usr/local/nagios/bin/nrpe -c /usr/local/nagios/etc/nrpe.cfg -d")

        print green("upload File success and restart nagios  service!")

        #这里以绿色字体打印结果是为了方便查看脚本执行结果


for host in hosts:

    env.host_string = host

    put_task()

执行上面的脚本以后,Fabric也会返回清晰的显示结果,大家可以根据显示结果得知哪些机器已经成功运行,哪些机器失败,非常直观,结果如下所示:

Put Local File to remote

[bidder1] put: /home/ec2-user/check_cpu_utili.sh -> /home/ec2-user/check_cpu_utili.sh

[bidder1] sudo: cp /home/ec2-user/check_cpu_utili.sh  /usr/local/nagios/libexec/check_cpu_utili.sh

[bidder1] sudo: chown nagios:nagios /usr/local/nagios/libexec/check_cpu_utili.sh

[bidder1] sudo: chmod +x /usr/local/nagios/libexec/check_cpu_utili.sh

[bidder1] sudo: kill `ps aux | grep nrpe | head -n1 | awk '{print $2}' `

[bidder1] sudo: /usr/local/nagios/bin/nrpe -c /usr/local/nagios/etc/nrpe.cfg -d

upload File success and restart nagios  service!

Put Local File to remote

[bidder2] put: /home/ec2-user/check_cpu_utili.sh -> /home/ec2-user/check_cpu_utili.sh

[bidder2] sudo: cp /home/ec2-user/check_cpu_utili.sh  /usr/local/nagios/libexec/check_cpu_utili.sh

[bidder2] sudo: chown nagios:nagios /usr/local/nagios/libexec/check_cpu_utili.sh

[bidder2] sudo: chmod +x /usr/local/nagios/libexec/check_cpu_utili.sh

[bidder2] sudo: kill `ps aux | grep nrpe | head -n1 | awk '{print $2}' `

[bidder2] sudo: /usr/local/nagios/bin/nrpe -c /usr/local/nagios/etc/nrpe.cfg -d

upload File success and restart nagios  service!

Put Local File to remote

[bidder3] put: /home/ec2-user/check_cpu_utili.sh -> /home/ec2-user/check_cpu_utili.sh

[bidder3] sudo: cp /home/ec2-user/check_cpu_utili.sh  /usr/local/nagios/libexec/check_cpu_utili.sh

[bidder3] sudo: chown nagios:nagios /usr/local/nagios/libexec/check_cpu_utili.sh

[bidder3] sudo: chmod +x /usr/local/nagios/libexec/check_cpu_utili.sh

[bidder3] sudo: kill `ps aux | grep nrpe | head -n1 | awk '{print $2}' `

[bidder3] sudo: /usr/local/nagios/bin/nrpe -c /usr/local/nagios/etc/nrpe.cfg -d

upload File success and restart nagios  service!

Put Local File to remote

[bidder4] put: /home/ec2-user/check_cpu_utili.sh -> /home/ec2-user/check_cpu_utili.sh

[bidder4] sudo: cp /home/ec2-user/check_cpu_utili.sh  /usr/local/nagios/libexec/check_cpu_utili.sh

[bidder4] sudo: chown nagios:nagios /usr/local/nagios/libexec/check_cpu_utili.sh

[bidder4] sudo: chmod +x /usr/local/nagios/libexec/check_cpu_utili.sh

[bidder4] sudo: kill `ps aux | grep nrpe | head -n1 | awk '{print $2}' `

[bidder4] sudo: /usr/local/nagios/bin/nrpe -c /usr/local/nagios/etc/nrpe.cfg -d

upload File success and restart nagios  service!

Put Local File to remote

[bidder5] put: /home/ec2-user/check_cpu_utili.sh -> /home/ec2-user/check_cpu_utili.sh

[bidder5] sudo: cp /home/ec2-user/check_cpu_utili.sh  /usr/local/nagios/libexec/check_cpu_utili.sh

[bidder5] sudo: chown nagios:nagios /usr/local/nagios/libexec/check_cpu_utili.sh

[bidder5] sudo: chmod +x /usr/local/nagios/libexec/check_cpu_utili.sh

[bidder5] sudo: kill `ps aux | grep nrpe | head -n1 | awk '{print $2}' `

[bidder5] sudo: /usr/local/nagios/bin/nrpe -c /usr/local/nagios/etc/nrpe.cfg -d

upload File success and restart nagios  service!

大家可以看到,短短几行代码就达到了自动化运维的效果,而且跟Fabric相关的代码都是纯Python代码和Shell代码,开发人员和运维人员很容易上手,在公司里推广应用,大家的认可程度也高。事实上,通过上面的举例大家应该能发现,Fabric特别适合于需要重复执行大量Shell命令的工作场景。

3.8 小结

Fabric作为Python开发的轻量级运维工具,小块头却有大智慧,熟练掌握其用法能够解决工作中的很多自动化运维需求,这应该也是它受到运维人员和开发人青睐的原因。大家可以通过在开发环境和线上环境的应用示例,熟悉掌握相关用法,然后将其应用于自己的系统自动化运维环境。


版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:

华章出版社

官方博客
官网链接