跳到主要内容

开篇

· 阅读需 1 分钟

重新开始写博客,这篇作为开篇,继忙碌地工作了快两年之后,发现抽时间将一些想法和经验沉淀下来也是如此重要。而整理文章的过程本身也是对思绪的整理,可以帮助我们更好的理解所做的事情。 接下来会把旧博客的备份文章重新放上来。

CENTOS7管理之动态防火墙FIREWALLD

· 阅读需 8 分钟

firewalld 的出现带来了许多新的亮点,它使得防火墙规则管理更加方便和统一,但 firewalld 项目本身还有诸多不完善的地方,还需要一些时间的沉淀才能变得更加稳定和得到更多软件社区的支持。在我们使用过程中也发现了其存在的一些问题,我们也正在努力参与到 firewalld 的改进和测试当中,也希望有更多的人能够参与进来。

firewalld的出现

自 fedora18 开始,firewalld 已经成为默认的防火墙管理组件被集成到系统中,用以取代 iptables service 服务,而基于 fedora18 开发的RHEL7,以及其一脉相承的CentOS7 也都使用 firewalld 作为默认的防火墙管理组件,无论是对于日常使用还是企业应用来讲这样的变更都或多或少的为使用者带来了影响。在浏览完本文前我们先不急着下结论到底这样的变化是好是坏,让我们“剥离它天生的骄傲,排除这些外界的干扰“,来看看 firewalld 到底是什么样的。 以下内容以 CentOS7 系统为例进行讲解,我们假设读者对于 iptables 防火墙相关知识有一定的掌握。

什么是动态防火墙?

我们首先需要弄明白的第一个问题是到底什么是动态防火墙。为了解答这个问题,我们先来回忆一下 iptables service 管理防火墙规则的模式:用户将新的防火墙规则添加进 /etc/sysconfig/iptables 配置文件当中,再执行命令 service iptables reload 使变更的规则生效。在这整个过程的背后,iptables service 首先对旧的防火墙规则进行了清空,然后重新完整地加载所有新的防火墙规则,而如果配置了需要 reload 内核模块的话,过程背后还会包含卸载和重新加载内核模块的动作,而不幸的是,这个动作很可能对运行中的系统产生额外的不良影响,特别是在网络非常繁忙的系统中。

如果我们把这种哪怕只修改一条规则也要进行所有规则的重新载入的模式称为静态防火墙的话,那么 firewalld 所提供的模式就可以叫做动态防火墙,它的出现就是为了解决这一问题,任何规则的变更都不需要对整个防火墙规则列表进行重新加载,只需要将变更部分保存并更新到运行中的 iptables 即可。

这里有必要说明一下 firewalld 和 iptables 之间的关系, firewalld 提供了一个 daemon 和 service,还有命令行和图形界面配置工具,它仅仅是替代了 iptables service 部分,其底层还是使用 iptables 作为防火墙规则管理入口。firewalld 使用 python 语言开发,在新版本中已经计划使用 c++ 重写 daemon 部分。

firewalld 具有哪些特性?

那么 firewalld 除了是动态防火墙以外,它还具有哪些优势或者特性呢?第一个是配置文件。firewalld 的配置文件被放置在不同的 xml 文件当中,这使得对规则的维护变得更加容易和可读,有条理。相比于 iptables 的规则配置文件而言,这显然可以算作是一个进步。第二个是区域模型。firewalld 通过对 iptables 自定义链的使用,抽象出一个区域模型的概念,将原本十分灵活的自定义链统一成一套默认的标准使用规范和流程,使得防火墙在易用性和通用性上得到提升。令一个重要特性是对 ebtables 的支持,通过统一的接口来实现 ipt/ebt 的统一管理。还有一个重要特性是富语言。富语言风格的配置让规则管理变得更加人性化,学习门槛相比原生的 iptables 命令有所降低,让初学者可以在很短时间内掌握其基本用法,规则管理变得更快捷。

firewalld 基本术语

本文不打算重复阐述现有文档中的基本知识,仅仅提供一些对现有文档中知识的补充和理解,详细的文档请参阅man page或本文结尾处的延伸阅读1部分。

zone:

firewalld将网卡对应到不同的区域(zone),zone 默认共有9个,block dmz drop external home internal public trusted work ,不同的区域之间的差异是其对待数据包的默认行为不同,根据区域名字我们可以很直观的知道该区域的特征,在CentOS7系统中,默认区域被设置为public,而在最新版本的fedora(fedora21)当中随着 server 版和 workstation 版的分化则添加了两个不同的自定义 zone FedoraServer 和 FedoraWorkstation 分别对应两个版本。使用下面的命令分别列出所有支持的 zone 和查看当前的默认 zone:

firewall-cmd --get-zones
firewall-cmd --get-default-zone

所有可用 zone 的 xml 配置文件被保存在 /usr/lib/firewalld/zones/ 目录,该目录中的配置为默认配置,不允许管理员手工修改,自定义 zone 配置需保存到 /etc/firewalld/zones/ 目录。防火墙规则即是通过 zone 配置文件进行组织管理,因此 zone 的配置文件功能类似于 /etc/sysconfig/iptables 文件,只不过根据不同的场景默认定义了不同的版本供选择使用,这就是 zone 的方便之处。

service:

在 /usr/lib/firewalld/services/ 目录中,还保存了另外一类配置文件,每个文件对应一项具体的网络服务,如 ssh 服务等,与之对应的配置文件中记录了各项服务所使用的 tcp/udp 端口,在最新版本的 firewalld 中默认已经定义了 70+ 种服务供我们使用,当默认提供的服务不够用或者需要自定义某项服务的端口时,我们需要将 service 配置文件放置在 /etc/firewalld/services/ 目录中。service 配置的好处显而易见,第一,通过服务名字来管理规则更加人性化,第二,通过服务来组织端口分组的模式更加高效,如果一个服务使用了若干个网络端口,则服务的配置文件就相当于提供了到这些端口的规则管理的批量操作快捷方式。每加载一项 service 配置就意味着开放了对应的端口访问,使用下面的命令分别列出所有支持的 service 和查看当前 zone 种加载的 service:

firewall-cmd --get-services
firewall-cmd --list-services

使用示例

在 firewalld 官方文档中提供了若干使用示例,这些示例对学习防火墙管理是很好的基本参考资料。接下来我们将通过一些真实的使用示例来展示如何使用 firewalld 对防火墙规则进行管理。

场景一:自定义 ssh 端口号

出于安全因素我们往往需要对一些关键的网络服务默认端口号进行变更,如 ssh,ssh 的默认端口号是 22,通过查看防火墙规则可以发现默认是开放了 22 端口的访问的:

[root@localhost ~]# iptables -S
... ...
-A IN_public_allow -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT

假设自定义的 ssh 端口号为 22022,使用下面的命令来添加新端口的防火墙规则:

firewall-cmd --add-port=22022/tcp

如果需要使规则保存到 zone 配置文件,则需要加参数 --permanent。我们还可以使用自定义 service 的方式来实现同样的效果: 在 /etc/firewalld/services/ 目录中添加 自定义配置文件 custom-ssh.xml ,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<service>
<short>customized SSH</short>
<description>Secure Shell (SSH) is a protocol for logging into and executing commands on remote machines. It provides secure encrypted communications. If you plan on accessing your machine remotely via SSH over a firewalled interface, enable this option. You need the openssh-server package installed for this option to be useful.</description>
<port protocol="tcp" port="22022"/>
</service>

执行命令重载配置文件,并添加防火墙规则:

systemctl reload firewalld
firewall-cmd --add-service=custom-ssh

一旦新的规则生效,旧的 ssh 端口规则旧可以被禁用掉:

firewall-cmd --remove-service=ssh

场景二:允许指定的IP访问SNMP服务

某些特殊的服务我们并不想开放给所有人访问,只需要开放给特定的IP地址即可,例如 SNMP 服务,我们将使用 firewalld 的富语言风格配置指令:

firewall-cmd --add-rich-rule="rule family='ipv4' source address='10.0.0.2' port port='161' protocol='udp' accept"

查看防火墙规则状态,证明结果正是我们想要的:

[root@localhost ~]# iptables -S
... ...
-A IN_public_allow -s 10.0.0.2/32 -p udp -m udp --dport 161 -m conntrack --ctstate NEW -j ACCEPT

参考链接:2

Footnotes

  1. https://fedoraproject.org/wiki/FirewallD/zh-cn

  2. https://access.redhat.com/documentation/zh-CN/Red_Hat_Enterprise_Linux/7/html/Security_Guide/sec-Using_Firewalls.html

NGINX性能调优简要

· 阅读需 9 分钟

以下是数天前NGINX官方博客发表的一篇有关nginx性能优化的文章,内容简明扼要,值得一读。译文在原文基础上略有变动,如有不足,欢迎指正。

前言

NGINX作为一款高性能负载均衡器,缓存和web服务器被大家所熟知,为全世界40%的网站提供着服务。NGINX和Linux系统中的大多数默认配置对普通应用来说已经很合适,然而要想达到更高的性能以应对高负载场景那么一些性能优化是必须的。这篇文章将会探讨一些在性能调优时涉及到的NGINX和Linux系统参数。当然可供调节的参数众多,我们这里只会涉及部分对大多数用户来说被使用最多的。其他没有被涉及到的参数多是需要对系统有深入了解之后才需要接触到的。

简介

我们假定此文读者对NGINX的基本架构和配置有一定了解,此文不会重复NGINX文档中的内容,如有涉及我们仅会给出链接。

在做性能调优时一个最佳实践是一次只动一个参数,如果调整后未达到预期效果,则将其修改回初始值。我们将从Linux系统的一些参数开始讲起,这些参数将对后续的NGINX配置调整有着至关重要的作用。

Linux 配置

现代Linux内核(2.6+)中的很多参数设置都是十分到位的,但是仍然有一些是需要我们进行调整的。如果一些默认的值设置得太小,那么在内核日志中将记录着错误信息,这预示着我们需要对其中一些参数进行调整了。在众多选项当中我们只会涉及到对大多数负载场景都实用的,请参考Linux系统文档以了解更多有关这些选项的细节。

Backlog Queue

下面的设置与网络连接和连接队列有直接关联。如果你有大量的接入请求,且偶尔出现一些失效请求的话,那么下面的设置将起到优化效果。

net.core.somaxconn

此参数控制NGINX等待连接的队列大小。因NGINX处理连接的速度快,所以一般这个参数不建议被设置太大,但是默认值有点偏低,所以针对大流量网站来说调整这个参数是必须的,如果此参数被设置多低那么有可能在内核日志中看到报错,这时需要调整此参数直到报错消失为止。注意,如果此参数设置大于512的话,则需要对NGINX配置中的listen指令中的backlog参数进行同步调整。

net.core.netdev_max_backlog

此参数控制数据包在进入CPU处理前,在网卡中被缓存的量,需要处理大带宽的机器需要增加此参数的值。设置此参数需要参考具体的网卡的文档或者根据系统错误日志进行调整。

File Descriptors

文件描述符是系统在处理如网络连接和打开文件时的系统资源。NGINX中每个连接的建立可能需要占用两个文件描述符,例如代理模式下,一个用来处理客户端连接,一个用来处理到后端的连接,而如果HTTP keepalives被启用的话对文件描述符的消耗会轻松一些。需要处理高并发的机器建议调整下面的参数:

sys.fs.file_max

这个参数影响系统全局的文件描述符打开数量限制。

nofile

此参数影响单个用户的文件描述符数量,在/etc/security/limits.conf中进行设置。

Ephemeral ports

当NGINX被作为代理服务器时,每个到后端服务器的连接将占用一个临时的,或短期的端口。

net.ipv4.ip_local_port_range

此参数控制可被用作临时端口的起始范围,一个通用设置是1024-65000

net.ipv4.tcp_fin_timeout

此参数控制一个连接使用完毕后端口被回收再利用的超时时间,一般默认设置为60秒,但是一般设置降低到30或者15秒都是没有问题的。

NGINX 配置

下面介绍NGINX中对性能有影响的参数,如下面提到的一样,我们只讲解一些适用于大多数用户的参数进行调整,其他未提及的可能是不建议调整的。

Worker Processes

NGINX可以同时启动多个worker进程,每个进程处理大量的连接,通过调整下面的参数可以控制启动的进程数量和每个进程所处理的连接数量。

worker_processes

此参数控制NGINX启动进程的数量,在多数情况下每个cpu核心分配一个进程是最佳的,将参数值设置为auto即可达到。很多场景下都需要增加此参数的值,如在需要处理大量磁盘I/O的场景。默认的值是1。

worker_connections

此参数控制在同一时刻单个进程可以处理的最大连接数。默认值512,但大多数系统都可以处理更大的量。其最佳值与实际场景和系统有关,需要经过反复测试才能得出。

Keepalives

Keepalive连接降低连接的建立和关闭对CPU和网络的消耗,对性能有着十分重要的影响。NGINX会关闭所有客户端的连接,且与后端服务器的连接都是独立的。NGINX支持到客户端和后端的keepalive,下面的参数对客户端keepalive进行控制:

keepalive_requests

此参数控制通过单个keepalive连接可以处理的客户端请求数量,默认值是100,可以调整为更大的值,特别是在通过单个客户端进行压力测试的时候。

keepalive_timeout

此参数控制单个keepalive连接在空闲状态保持连接的时间。

下面的参数对后端keepalive进行控制:

keepalive

此参数控制单个worker进程保持到upstream server的keepalive连接数量,且默认没有设置。如需启动到后端的keepalive连接则需要进行如下设置:

proxy_http_version 1.1; proxy_set_header Connection "";

Access Logging

请求产生的日志记录对CPU和I/O都有消耗,一个可以降低资源消耗的办法是启用日志缓存。启用日志缓存后,NGINX将缓存部分请求日志,然后一次性写入文件。要启用日志缓存只需要在access_log中增加“buffer=size”的设置即可,size值控制缓存的大小,同时也可以设置“flush=time”以控制缓存的时间,设置了两个参数后,NGINX会在缓存被充满或者日志条目数达到flush值时回写日志。在worker进程重启或者关闭时也会回写日志。而永久禁用日志记录也是可以实现的。

Sendfile

Sendfile 是一项操作系统特性,可以被NGINX启用。它通过对数据在文件描述符之间进行in-kernel copying来提供更快的tcp数据传输处理,一般通过zero-copy技术实现。NGINX使用它来完成缓存数据或者磁盘数据到socket的写操作,不产生任何的用户空间上下文切换开销,可降低CPU负载和提高处理速度。当启用sendfile特性之后,由于数据不经过用户空间,使得对数据内容进行处理的filter将不起作用,例如gzip filter将默认被禁用。

Limits

NGINX也为用户提供设置连接限制的能力,用来对客户请求的资源进行控制,对系统性能,用户体验和安全性也产生极大的影响。下面是部分用于请求限制的指令:

limit_conn/limit_conn_zone

这两个指令用来控制NGINX可接收的连接数,例如来自单个客户端IP的连接请求。这有助于限制客户端建立过多的连接并消耗过多资源。

limit_rate

这个指令控制单个连接允许的客户端最大带宽。这可以避免系统被部分客户端耗尽资源,保证了为每个客户端请求提供服务的质量。

limit_req/limit_req_zone

这两个指令可以控制NGINX的请求回复水平,以不至于被部分客户端拖垮。也被用来加强安全性,特别是对登陆页面等进行有效的保护。

max_conns

这个指令用来控制到后端服务器的最大连接数,保护后端服务器不被拖垮,默认值是zero,没有任何限制。

queue

如果max_conns被设置,那么此参数对超过最大连接时的状态产生影响,可以设置队列中请求的个数和缓冲时间,如果没有设置此参数,则队列不存在。

其他设置

NGINX还有一些特性能对某些特定场景下的应用起到性能优化作用,我们将探讨其中的两个特性。

缓存

当把NGINX作为负载均衡器来使用的场景下,启用cache可以显著改善到客户端的响应时间,且显著降低后端服务器的压力。如需了解更多NGINX的caching设置,可以参考此链接:: NGINX Admin Guide – Caching.

压缩

对回应内容进行压缩将有效降低回应内容的大小,降低带宽消耗,然而压缩将增加CPU的开销,所以带宽成本较高时启用才是明智之举。需要明确注意的是不要对已经压缩的内容启用压缩,例如jpeg格式的图片,如需了解更多有关压缩的设置可以参考此文档: NGINX Admin Guide – Compression and Decompression

原文链接:

http://nginx.com/blog/tuning-nginx/

shell脚本coding style总结

· 阅读需 4 分钟

这里总结了个人比较推崇的shell脚本coding style,编写出方便阅读和维护的脚本是运维人员的基本操守。

1.关于缩进: 一个tab定义为4个空格; 关于这个缩进距离貌似有太多的的说法了,有的会用8个空格,有的用2个空格,还有的用4个空格;不过最终取决于团队风格。但是记住,正确的做法是项目、文件、成员间全部统一,千万不要出现一个项目内各种缩进,tab和空格混用,甚至一个文件中的缩进都不统一的情况。在vim中设置如下:

set ts=4
set sw=4
set expandtab

2.尽量缩短单行的长度,最好不超过72字符(注意:这个限制因历史原因导致,为了兼容那些老的终端设备而考虑,实际上现在已经不适用了,单行代码可以更长,只要不要超过大多数屏幕的输出宽度就好)

bad:

thisisaverylongline || thisisanotherlongline

good:

thisisaverylongline ||
thisisanotherlongline

3.注释尽量保持清晰的层次,#号与注释文本间保持一个空格以示和代码的区分

bad:

#this is a comment
#this is a code line

good:

# this is a comment
#this is code line

4.定义函数可以不用写function关键字,函数名字尽量短小无歧义,尽量传递返回值

bad:

function  cmd1

good:

cmd_start() {
dosomethinghere
return 0
}

5.全局变量用大写,局部变量用小写,函数内尽量不要使用全局变量,以免混淆导致变量覆盖,注意尽量使用小写表示变量

bad:

foo() {
a=2
echo $a
}
a=1
echo $a

good:

foo(){
local a
a=2
echo $a
}
A=1
echo $A

6.使用内建的[] 、[[]] 进行条件测试,避免使用test命令

bad:

test -f filename

good:

[ -f filename ]
[[ -n $string ]]

7.使用$(())进行普通运算,尽量避免使用expr或其他外部命令 $[]也可用于计算

bad:

num=$(expr 1 + 1)

good:

num=$((1+1))
num=$[1+2]

8.管道符左右都应加空格,重定向符空格加左不加右

bad:

find /data -name tmp*|xargs rm -fr
cat a>b

good:

find /data -name tmp* | xargs rm -fr
cat a >b

9.当source命令属于外部命令的时候,我们应尽量使用. ,当视力不好怕写错看错的时候,应尽量不用.而用source,在实际使用中我们发现.号极难辨认,这里推荐使用source命令,更方便维护。

bad:

. func_file

good:

source func_file

10.if 和 then 之间使用分号+空格分隔,不要用换行,书写上和c style类似:

bad:

grep string logfile
if [ $? -ne 0 ]
then
dosomething
fi

while 1
do
do something here
done

good:

if grep string logfile; then
dosomething
fi

while 1; do
do something here
done

注意;和then间有一个空格的距离

11.如果grep能直接处理文件输入那就不要和cat连用; 如果wc能直接从文件统计就不要和cat连用; 如果grep能统计行数就不要和wc连用

bad:

cat file | grep tring
cat file.list | wc -l
grep avi av.list | wc -l

good:

grep tring file
wc -l file.list
grep -c avi av.list

12.如果awk的时候需要搜索恰好awk又能搜索,那么就不要再和grep连用

bad:

dosomething | grep tring | awk '{print $1}'

good:

dosomething | awk '/tring/' '{print $1}'

13、尽量编写兼容旧版本shell风格且含义清晰的代码,不推荐不兼容写法或者不方便他人维护的代码

bad:

do_some_thing |& do_another_thing
do_some_thing &>> some_where

good:

do_some_thing 2>&1 | do_another_thing
do_some_thing >some_where 2>&1

注意 : 以上仅代表个人习惯和理解,并不能适用于所有人,适合的才是最好的。

拓展阅读文档:

Systemd基本使用介绍

· 阅读需 12 分钟

但是由于计算机软硬件的不断发展,人们逐渐发现sysvinit所提供的功能已经无法满足当前的需求,服务多年的sysvinit终将过时,于是一些替代的方案开始出现,这其中包括upstart ,systemd等。

关于PID 1

在linux的世界里,第一个启动到用户空间的进程名叫init,其pid为1,init进程启动完毕后,它相当于其他进程的根,负责将其他的服务进程启动起来,最终启动成为一个完整可用的系统。而提供这个init程序的软件名为sysvinit,它在整个linux系统中所担任的角色重要程度无可厚非。但是由于计算机软硬件的不断发展,人们逐渐发现sysvinit所提供的功能已经无法满足当前的需求,服务多年的sysvinit终将过时,于是一些替代的方案开始出现,这其中包括upstart ,systemd等。

init的责任

为什么说sysvinit会过时呢?我们从用户需求的角度来看不难发现,其实我们对init的最原始最核心需求是,将系统从内核引导至用户空间,而由于现在硬件水平的发展,使得我们在单纯的原始需求之上产生了新的或者更多的需求,那就是不仅要引导系统,而且要快速的引导系统。而早在sysvinit诞生的那个时候启动速度的重要程度似乎不是很高,所以它在这方面显得有些老是很自然的。现在让我们继续思考一下如何才能实现快速引导。试想,当一个系统启动的时候需要启动长长的一列服务,这样的系统启动速度应该不会快到哪里去,如大多数人使用桌面系统的情况一样,一个加快系统启动速度的最直接方法就是减少启动项,把不必要的启动项禁止掉。而另外一个方法则实现起来不像第一个这么简单,那就是并行启动。并行启动可以带来的速度提升显而易见,我们只需要尽可能保证让更多的服务可以并行启动即可。而从其他方面来看sysvinit由于对脚本的依赖导致启动完毕所有服务过程中需要大量的执行外部命令,这一点也将导致引导速度的变慢。

后起之秀

当一项标准已经无法满足当下发展的需求时,最好的办法是打破陈规让自己变成新的标准。systemd就是这么做的,因为现实的需要它已经不能顾及POSIX标准了,以不至于阻碍其更好的发展,而systemd在设计之初也和苹果的launchd在某些地方不谋而合。尽管在最初的时候这种做法没有得到所有人的认可甚至是惹恼了一些家伙,但是现在看来systemd已经成为了事实上的标准了,现在已经采用systemd的发行版有fedora,opensuse,debian,ubuntu,rhel7,archlinux等,尽管在systemd的推广过程中发生了很多曲折,但最终大家的选择是一致的---朝着更好的方向发展而不是墨守陈规。 在功能上,systemd不仅仅是解决了现有程序的种种问题,而且包含了大量新的特性,这意味着系统中原本需要的很多组件现在也都可以下岗了,下面来看看systemd的一些特性:

  • 服务管理 - 提供了统一的启动/停止/重启/重载 而不再需要编写一大坨脚本来干这种原本就应该十分简单基本且重要的事情,取而代之的是更为简洁的配置文件,这也表示今后的系统中各种service启动控制的统一性,使得daemon服务开发者免于编写各种看上去参差不齐的启动脚本,增加了通用性。
  • socket管理 - 支持监听socket,这使得socket的监听和服务本身可以相互独立,一方面可以以此提高服务启动速度,另外一方面还可以节约系统开销。
  • 设备管理 - 可以配合udev规则对设备进行管理,还可以配合/etc/fstab文件实现磁盘的挂载管理,甚至实现更高级的自动挂载;
  • target分组 - 把不同的unit组合起来,组合到同一个target里面,完全取代sysvinit的运行等级的概念;
  • 状态快照 - 可以对当前系统中的unit状态进行快照,这个概念有些类似于系统的休眠与恢复,你可以让系统从一个状态切换进入另外一个状态;

systemd管理入门

下面我们将通过一些例子来演示一些基本的管理命令,这些命令对于系统管理员来说至关重要。

1 如何检查启动项

任何启动项,只要是在系统启动时有被执行到,不论启动成功还是失败,systemd都能够记录下他们的状态,直接执行不带参数的systemctl命令即可观察到,如下:

# systemctl
UNIT LOAD ACTIVE SUB DESCRIPTION
proc-sys-fs-binfmt_misc.automount loaded active waiting Arbitrary Executable File Formats File System Aut
sys-devices-pci...0-backlight-acpi_video1.device loaded active plugged /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0
sys-devices-pci...0-backlight-acpi_video0.device loaded active plugged /sys/devices/pci0000:00/0000:00:02.0/backlight/ac
sys-devices-pci...00-0000:00:19.0-net-em1.device loaded active plugged 82579LM Gigabit Network Connection
sys-devices-pci...d1.4:1.0-bluetooth-hci0.device loaded active plugged /sys/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1
sys-devices-pci...000:00:1b.0-sound-card0.device loaded active plugged 6 Series/C200 Series Chipset Family High Definiti
sys-devices-pci...0000:03:00.0-net-wlp3s0.device loaded active plugged RTL8188CE 802.11b/g/n WiFi Adapter
sys-devices-pci...-0:0:0:0-block-sda-sda1.device loaded active plugged ST9500420AS
sys-devices-pci...-0:0:0:0-block-sda-sda2.device loaded active plugged ST9500420AS
sys-devices-pci...-0:0:0:0-block-sda-sda3.device loaded active plugged ST9500420AS
sys-devices-pci...-0:0:0:0-block-sda-sda5.device loaded active plugged ST9500420AS
sys-devices-pci...-0:0:0:0-block-sda-sda6.device loaded active plugged ST9500420AS
sys-devices-pci...-0:0:0:0-block-sda-sda7.device loaded active plugged LVM PV EKHM59-PY9G-AoRX-Nr9k-nnxN-XxxO-DFcj4N on
sys-devices-pci...0:0:0-0:0:0:0-block-sda.device loaded active plugged ST9500420AS
sys-devices-pci...-1:0:0:0-block-sdb-sdb1.device loaded active plugged KINGSTON_SVP200S360G
sys-devices-pci...-1:0:0:0-block-sdb-sdb2.device loaded active plugged LVM PV rlRqSb-IlQn-DJQi-i7fg-sUV5-3bjI-g2npg7 on
sys-devices-pci...1:0:0-1:0:0:0-block-sdb.device loaded active plugged KINGSTON_SVP200S360G

要检查具体的服务,则使用status选项加上服务名即可,如下:

# systemctl status libvirtd
libvirtd.service - Virtualization daemon
Loaded: loaded (/usr/lib/systemd/system/libvirtd.service; enabled)
Active: active (running) since 一 2014-04-07 19:10:30 CST; 9min ago
Docs: man:libvirtd(8)
http://libvirt.org
Main PID: 1673 (libvirtd)
CGroup: /system.slice/libvirtd.service
├─1673 /usr/sbin/libvirtd
└─1804 /sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf


4月 07 19:10:33 localhost.localdomain dnsmasq[1804]: using nameserver 218.6.200.139#53
4月 07 19:10:33 localhost.localdomain dnsmasq[1804]: using nameserver 61.139.2.69#53
4月 07 19:10:33 localhost.localdomain dnsmasq[1804]: using local addresses only for unqualified names
4月 07 19:10:33 localhost.localdomain dnsmasq[1804]: read /etc/hosts - 3 addresses
4月 07 19:10:33 localhost.localdomain dnsmasq[1804]: read /var/lib/libvirt/dnsmasq/default.addnhosts - 0 addresses
4月 07 19:10:33 localhost.localdomain dnsmasq-dhcp[1804]: read /var/lib/libvirt/dnsmasq/default.hostsfile
Hint: Some lines were ellipsized, use -l to show in full.

以上这些信息向我们展示了服务的运行时间,当前状态,相关进程pid,以及最近的有关该服务的日志信息,是不是很方便?

2 找出每项服务所对应的进程id

对于系统管理来说这是最常见的工作,但是在sysvinit时代我们只能借助例如ps命令等来完成,而systemd已经考虑到了系统管理员的需求,于是下面的命令诞生了:

# systemd-cgls
├─1 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
├─user.slice
│ ├─user-1000.slice
│ │ ├─session-1.scope
│ │ │ ├─1806 gdm-session-worker [pam/gdm-password]
│ │ │ ├─1820 /usr/bin/gnome-keyring-daemon --daemonize --login
│ │ │ ├─1822 gnome-session
│ │ │ ├─1830 dbus-launch --sh-syntax --exit-with-session
│ │ │ ├─1831 /bin/dbus-daemon --fork --print-pid 4 --print-address 6 --session
│ │ │ ├─1858 /usr/libexec/at-spi-bus-launcher
│ │ │ ├─1862 /bin/dbus-daemon --config-file=/etc/at-spi2/accessibility.conf --nofork --print-address 3
│ │ │ ├─1865 /usr/libexec/at-spi2-registryd --use-gnome-session
│ │ │ ├─1872 /usr/libexec/gvfsd
... ...

现在我们可以很清晰的看到哪项服务启动了哪些进程,对于系统管理来说十分有用;

3 如何正确的杀死服务进程

在sysvinit的时代,如果需要结束一个服务及其启动的所有进程,可能会遇到一些糟糕的进程无法正确结束掉,即便是我们使用kill,killall等命令来结束它们,到了systemd的时代一切都变得不一样,systemd号称是第一个能正确的终结一项服务的程序,下面来看看具体如何操作的:

systemctl kill crond.service

或者指定一个信号发送出去

systemctl kill -s SIGKILL crond.service

例如可以像这样执行一次reload操作

systemctl kill -s HUP --kill-who=main crond.service

4 如何停止和禁用一项服务

下面我们回顾一下在sysvinit时代执行下面的命令所实现的功能

# service ntpd stop
# chkconfig ntpd off

很简单,首先停止服务,其次禁用服务 那么在systemd中应该如何操作呢?

# systemctl stop ntpd.service
# systemdctl disable ntpd.service

很显然systemctl命令已经取代了service 和chkconfig两个命令的位置,不光如此,我们还能将服务设置为连人工也无法启动:

# ln -s /dev/null  /etc/systemd/system/ntpd.service
# systemctl daemon-reload

5 检查系统启动消耗时间

在过去我们可以借助第三方工具来实现对系统启动过程的耗时跟踪,现在这个功能已经集成到systemd当中,可以十分方便的了解到系统启动时在各个阶段所花费的时间:

# systemd-analyze
Startup finished in 1.669s (kernel) + 1.514s (initrd) + 7.106s (userspace) = 10.290s
以上信息简要的列出了从内核到用户空间整个引导过程大致花费的时间,如果要查看具体每项服务所花费的时间则使用如下命令:
# systemd-analyze blame
6.468s dnf-makecache.service
5.556s network.service
1.022s plymouth-start.service
812ms plymouth-quit-wait.service
542ms lvm2-pvscan@8:7.service
451ms systemd-udev-settle.service
306ms firewalld.service
246ms dmraid-activation.service
194ms lvm2-pvscan@8:18.service
171ms lvm2-monitor.service
145ms bluetooth.service
135ms accounts-daemon.service
113ms rtkit-daemon.service
111ms ModemManager.service
104ms avahi-daemon.service
102ms systemd-logind.service
79ms systemd-vconsole-setup.service
77ms acpid.service

输出结果类似上面这些内容,至此我们可以看到系统里每一项服务的启动时间,据此可以对系统引导过程了如指掌,如果你觉得这还不够直观,那么可以将结果导出到图像文件里面:

systemd-analyze plot >systemd.svg

该命令会将系统的启动过程输出到一张svg图像上面,更直观的展现出各个服务启动所花费的时间,在我们需要分析和优化服务启动项的时候很有帮助。

6 查看各项服务的资源使用情况

和top命令不一样,top命令更侧重于展示以进程为单位的资源状态,而systemd提供了一个命令来方便的查看每项服务的实时资源消耗状态:

# systemd-cgtop
Path Tasks %CPU Memory Input/s Output/s
/ 199 12.3 1.9G - -
/system.slice/ModemManager.service 1 - - - -
/system.slice/abrt-oops.service 1 - - - -
/system.slice/abrt-xorg.service 1 - - - -
/system.slice/abrtd.service 1 - - - -
/system.slice/accounts-daemon.service 1 - - - -
/system.slice/acpid.service 1 - - - -
/system.slice/alsa-state.service 1 - - - -
/system.slice/atd.service 1 - - - -
/system.slice/auditd.service 3 - - - -
/system.slice/avahi-daemon.service 2 - - - -
/system.slice/bluetooth.service 1 - - - -
/system.slice/chronyd.service 1 - - - -
/system.slice/colord.service 1 - - - -
/system.slice/crond.service 1 - - - -

7 我们必须要注意到的配置文件变更

systemd考虑到各种发行版本的用户使用习惯,尽量提供更为通用的配置文件以方便各家统一,方便用户使用,下面列出一些基本的统一的配置文件:

  • /etc/hostname,debian和redhat在这个配置文件上的差异导致了系统管理或多或少的不便,此文件的统一意义重大
  • /etc/vconsole.conf,此文件用来统一管理console和键盘映射
  • /etc/locale.conf 配置系统环境语系
  • /etc/modules-load.d/*.conf 内核模块加载配置文件
  • /etc/sysctl.d/*.conf 内核参数配置文件,对/etc/sysctl.conf的扩充
  • /etc/tmpfiles.d/*.conf 运行态临时文件配置
  • /etc/os-release /etc/machine-id /etc/machine-info 这三个文件的统一对系统管理员来说也是意义深远,它让我们有了统一的检测发行版本等信息的入口

以上内容仅仅让各位对systemd形成基本的认识,我们将在后期的文章中更加深入地讨论systemd的特性。最后,再次感谢作者Lennart的贡献。

参考链接:

使用fpm2来管理ssh密码

· 阅读需 3 分钟

不知道有没有童鞋需要管理一堆ssh口令,5个以内靠脑子记可能是个好办法,但是如果10个100个的时候恐怕脑子记有点不太够用。

不知道有没有童鞋需要管理一堆ssh口令,5个以内靠脑子记可能是个好办法,但是如果10个100个的时候恐怕脑子记有点不太够用, 这个时候就需要借助外部工具来进行管理,当然你可以自己写个简单的脚本,把ssh账号密码写入一个list里面, 人懒且为了省事也可以使用一些现成的工具,下面就推荐一个图形界面的小工具给需要的童鞋:fpm2 fpm2全名Figaro's Paaword Manager 2,是一个开源软件,使用GNU General Public License Version 2 协议,这里是官方地址,它还有android版本的。

在fedora里面可以直接yum安装:

yum install fpm2

安装完成后第一次运行需要你输入一个密码,今后每次启动fpm2的时候就用这个密码,默认如果密码输入错误次数超过3次,则你懂的。

打开fpm2后,一看就明白如何使用,它支持ssh/web以及自定义的密码管理,可以对管理的服务器进行分类,十分方便。其亮点是你可以根据自己的需要设置launcher,默认双击建立好的口令就会自动执行launcher定义的命令;

launcher里面将保存的账号密码和IP/URL定义为参数,$a ip/url$u username$p password, 有了这些变量后自定义launcher就很方便了。

但是其默认的ssh的launcher是不支持直接双击list里面的项目就登陆进服务器的,这个时候需要另外一个小工具sshpass,它的用处是登陆ssh的时候可以把密码作为参数传递给ssh客户端,而不需要交互式输入密码,这个工具代码托管在sf,fedora里也可以yum安装:

yum install sshpass

光有这两个工具还不够,下面是将两个工具完美结合起来的关键:

在fpm2的settings选项卡下有launcher设置项,打开之后你会发现默认的ssh launcher,下面是我自定义的ssh的加载器命令:

配置launcher

gnome-terminal -e 'sh -c "'"sshpass -p '"'$p'"' ssh -p 22 $u@$a;sudo -s"'"'

特别注意里面的单双引号的写法,不然如果你的密码里有像%&$之类的特殊符号时是会出问题的,

gnome-terminal -e 'xxxxx' 这里是单引号

sh -c "'" xxxxx "'" 这里是两对双引号中包含的单引号

sshpass -p '"' xxx '"' 这里是两对单引号包含的双引号

ssh命令后面加个sudo -s的作用是当退出ssh连接时不会立即关闭当前的terminal终端

使用这个launcher可以通杀所有特殊字符的密码,在运行fpm2+sshpass的组合前请自己使用当前运行fpm2的账户登陆ssh一下远程服务器将服务器的publickey取回来,没有key的情况下sshpass无法工作的。至少我遇到的是这样。

CentOS中双网卡静态路由配置

· 阅读需 3 分钟

一个网卡的话不需要静态路由的,如果多个网卡的话可以手工配置静态路由,特别是多个网卡走不同的子网的时候。

来自网上搜索的方法

之前一直没有配置过两个网卡分别使用不同的IP,走不同的网关,google了下发现了下面的手工添加路由的脚本:

#!/bin/sh
ip route add 10.1.1.0/24 dev br0 src 10.1.1.10 table bond0
ip route add default via 10.1.1.1 dev br0 table bond0
ip rule add from 10.1.1.10/32 table bond0
ip rule add to 10.1.1.10/32 table bond0

ip route add 192.168.1.0/24 dev br1 src 192.168.1.10 table bond1
ip route add default via 192.168.1.1 dev br1 table bond1
ip rule add from 192.168.1.10/32 table bond1
ip rule add to 192.168.1.10/32 table bond1

来自红帽文档中的方法

后来想了想这样的问题系统肯定已经支持得很好了,只是没有找到配置方法,于是找了下红帽的文档,发现可以像下面这样配置:

配置静态路由

一个网卡的话不需要静态路由的,如果多个网卡的话可以手工配置静态路由,特别是多个网卡走不同的子网的时候。

route -n #查看当前路由信息

静态路由配置文件路径: /etc/sysconfig/network-scripts/route-interface_name 就和网卡的配置文件路径结构差不多,比如ifcfg-eth0变成了route-eth0。

eth0网卡的静态路由就保存在这个文件里面。这个文件可以有两种格式

  • IP命令参数格式
  • 网络/掩码指令格式

IP命令参数模式:

1)第一行定义默认路由:

default via X.X.X.X dev interface

X.X.X.X 是默认路由的IP. interface是可以连接到默认路由的网卡接口名.

2)静态路由一行一个:

X.X.X.X/X via X.X.X.X dev interface

X.X.X.X/X 是网络和掩码. X.X.X.X 和 interface 是各自网段的网关IP和网卡接口.

配置示例 route-eth0: 默认网关 192.168.0.1, 接口eth0. 两条静态路由到 10.10.10.0/24 和172.16.1.0/24 :

default via 192.168.0.1 dev eth0
10.10.10.0/24 via 10.10.10.1 dev eth1
172.16.1.0/24 via 192.168.0.1 dev eth0

网络/掩码指令格式:

route-interface文件的第二种格式.下面是样板:

ADDRESS0=X.X.X.X
NETMASK0=X.X.X.X
GATEWAY0=X.X.X.X

ADDRESS0=X.X.X.X 静态路由的网络编号. NETMASK0=X.X.X.X 为上面那行设置子网掩码 . GATEWAY0=X.X.X.X 能够连接到 ADDRESS0=X.X.X.X 这个网络的网关

配置示例 route-eth0: 默认网关 192.168.0.1, 接口 eth0. 两条到10.10.10.0/24 和172.16.1.0/24 的静态路由:

ADDRESS0=10.10.10.0
NETMASK0=255.255.255.0
GATEWAY0=10.10.10.1
ADDRESS1=172.16.1.0
NETMASK1=255.255.255.0
GATEWAY1=192.168.0.1

ADDRESS0, ADDRESS1, ADDRESS2, 这样的编号必须是一个接一个的数字。

libvirt中的网络管理实践

· 阅读需 10 分钟

逐步分析libvirt中的网络管理方法及实践,分析在nat网络中遇到的问题及解决思路

libvirt网络基本概念

libvirt默认使用了一个名为default的nat网络,这个网络默认使用virbr0作为桥接接口,使用dnsmasq来为使用nat网络的虚拟机提供dns及dhcp服务,dnsmasq生效后的配置文件默认保存在以下路径:

  • /var/lib/libvirt/dnsmasq/default.hostsfile mac&&ip绑定的配置文件
  • /var/lib/libvirt/dnsmasq/default.leases dhcp分配到虚拟机的ip地址列表
  • /var/lib/libvirt/network/default.xml default网络的配置文件

dnsmasq服务的启动脚本在/etc/init.d/dnsmasq ,但是我们如果手动使用此脚本来启动服务将会导致dnsmasq读取其自己的配置文件来启动此服务,因此这么做是不推荐的,因为这个服务完全由libvirtd在接管,当libvirtd服务启动的时候,它会将它管理的被标记为autostart的network一并启动起来,而启动network的时候就会自动调用dnsmasq并赋予其适宜的配置文件来运行服务。

使用libvirt管理的网络都会用到dnsmasq来产生相应的配置,比如定义了一个名为route110的network,那么这个route110将使用一个新的桥接接口virbr1来接入网络,并使用dnsmasq产生名为route110.hostsfile和route110.leases的配置文件。其实这里提到的virbr0和virbr1都是libvirt产生的虚拟网卡,其作用就相当于一个虚拟交换机,为虚拟机提供网络转发服务。

libvirt中网络的类型

首先分析一下libvirt所能提供的网络类型:isolated 和forwarding,其中,isolated意为绝对隔离的网络,也就是说处于此网络内的虚拟机对于外界是隔离的,这种模式可以用到一些特殊的场合,比如虚拟机只提供给内部使用,虚拟机只要求能相互通信而不需要与互联网通信。另外一类,forwarding,就是把虚拟机的数据forward到物理网络实现与外部网络进行通讯,其中forwarding又分为两种:nat和routed。

nat

就是把虚拟机的网络数据在经过物理机网络的时候进行ip伪装,这样所有虚拟机出去的网络数据都相当于是物理机出去的数据,也就是说,我们可以分配给使用nat网络的虚拟机一个内网ip,而这个内网ip的虚拟机访问出去的时候外部网络看到的是物理机的公网ip,这样做的用处就是实现多个虚拟机共享物理主机的公网ip,节省公网ip地址;如前所述,默认情况下libvirt已经提供了一个名为default的nat网络,在不需要进行任何配置的情况下使用default网络的虚拟机即可访问互联网,但是互联网却无法访问虚拟机提供的服务,这是因为default网络只对虚拟机的数据包进行了伪装,而没有进行dnat和snat。

需要注意的是libvirt所实现的这种nat网络是通过物理机的iptables规则来实现的,也即是在虚拟机数据经过nat表的postrouting链出去的时候对其进行了伪装。

routed

forwarding模式的另外一种,routed,就是将虚拟机的数据直接通过物理机route出去,和nat一样,也是需要一个virbr虚拟网卡接口来与外面进行通信,这种模式的不同之处在于虚拟机的数据没有经过伪装便直接交给了外部网络,也就是说,使用route模式网络的虚拟机可以使用公网ip地址,而物理机却恰恰在这个时候完全可以使用一个内网ip而不对外提供访问,这样,虚拟机的网卡仅仅把物理机当作一个route数据的工具,此模式应用的场合很多,比如需要让虚拟机运行在一个dmz网络中。但是使用route模式有诸多限制,例如物理机的网络接口不够用的情况下。

这里需要注意的是,nat模式和route模式的区别仅仅在于前者使用了iptables对虚拟机的数据包进行了伪装,而后者没有。

自定义routed网络

在实际的虚拟机使用过程中,我们可能会碰到下面的情况:

  • 1 使用nat网络的虚拟机也需要对外提供服务,
  • 2 物理机只有一个网卡和一个ip,而我们现在既需要通过这个网卡来管理虚拟机,又需要使用这个网卡来提供route网络。

当然你所能碰到的问题可能千奇百怪,也可能根本没有碰到过此类bt问题。下面的内容只作为分析和解决问题的思路,不能生搬。在了解了libvirt的网络管理模式之后,就可以自己动手解决这些限制,下面重点解释第二种问题的解决方法:

首先假定route网络使用的是virbr1虚拟网卡,而虚拟机使用virbr1来为虚拟机提供服务,而我本机又有了一个br0作为em1的桥接网卡来对外提供网络服务,br0的ip是192.168.1.51

首先禁用br0:

ifdown br0

并配置br0的onboot为no,配置文件为onboot=no

然后我们定义了一个名为route的网络,virbr1的ip设置为192.168.1.51 ,这样做的目的是让virbr1取代之前的br0.

<network>
<name>route</name>
<uuid>6224b437-386b-f510-11d5-58d58b1ce87a</uuid>
<forward mode='route'/>
<bridge name='virbr1' stp='on' delay='0' />
<mac address='52:54:00:C8:9F:07'/>
<ip address='192.168.1.51' netmask='255.255.255.0'>
<dhcp>
<range start='192.168.1.128' end='192.168.1.254' />
</dhcp>
</ip>
</network>

接着生成并启用该网络

virsh net-define route.xml
virsh net-start route
virsh net-autostart route
  • /etc/libvirt/qemu/networks/ virsh net-define的network会保存到这
  • /var/lib/libvirt/network/ net-start启动了的network同时也会会保存到这
  • /etc/libvirt/qemu/networks/autostart/ net-autostart的network同时也会保存到这

接下来,我们需要修改em1的配置并将其桥接到virbr1上

ifcfg-em1

DEVICE="em1"
ONBOOT="yes"
BRIDGE=virbr1

接着启动em1

ifup em1

至此em1就被桥接到了virbr1上,可以使用下面的命令检查

brctl show

现在我们需要在本机添加一条默认路由,不然虚拟机是访问不了外面的:

route add default gw 192.168.1.1 dev virbr1

这里的192.168.1.1是真实的路由。至此,问题已经解决了。

自定义nat网络

下面说说问题1的解决方法: 既然知道了nat出去的虚拟机只能访问外网而外网却不能访问进来,nat又是通过iptables来做的,也就是当libvirt每次启动的时候都会往iptables最前面插入自己的规则以保证nat的虚拟机能正常访问外网,那么我们是不是可以通过修改iptables的规则来实现呢,比如我们需要一个内网ip的虚拟机对外提供80服务,那么我们就把物理机的80端口映射到这台虚拟机的80端口上,因为我们的物理机是可以直接和虚拟机通信的,只是外网不能而已,下面添加规则:

iptables -t nat -A PREROUTING -p tcp -i virbr1 --dport 80  -j DNAT --to-destination 192.168.122.2:80

这样我们对外部访问80端口进来的数据进行了dnat,而出去的我们不用snat,只需要再添加如下规则:

iptables -I FORWARD -i virbr1 -o virbr0 -p tcp -m state --state NEW -j ACCEPT

至此问题看似得到解决,但是我们忽略了一个关键的问题,那就是每当libvirt启动的时候就会往表的最前面插入它自己的规则,而iptables的规则是有先后顺序的,也就是说,我们自己添加的规则在libvirtd服务重启之后即被libvirt定义的规则所淹没,怎么办呢,我现在只想到了这么一个方法,直接修改libvirtd的启动脚本,在它的规则生效之后插入我们自定义的规则:

vi /etc/init.d/libvirtd

start() {
echo -n $"Starting $SERVICE daemon: "
initctl_check

mkdir -p /var/cache/libvirt
rm -rf /var/cache/libvirt/*
KRB5_KTNAME=$KRB5_KTNAME daemon --pidfile $PIDFILE --check $SERVICE $PROCESS --daemon $LIBVIRTD_CONFIG_ARGS $LIBVIRTD_ARGS
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/$SERVICE
sleep 1
iptables -D FORWARD -i virbr1 -o virbr0 -p tcp -m state --state NEW -j ACCEPT
iptables -I FORWARD -i virbr1 -o virbr0 -p tcp -m state --state NEW -j ACCEPT
... ...

至此问题基本解决。

route网络转换nat网络

另外一个问题,我们前面有发现route和nat的网络区别仅仅是一个做了nat的iptables规则一个没有,那么我们可不可以自己在iptables里面添加相应的规则将route网络变身为nat网络呢?答案肯定是可以的,只需要添加上下面的规则即可,原理还请观看本文的同学自己分析,这里假设我们route网络给虚拟机分配的ip是192.168.100.0/24网段:

iptables -t nat -A POSTROUTING -s 192.168.100.0/24 -d ! 192.168.100.0/24 -j MASQUERADE
iptables -A FORWARD --destination 192.168.100.0/24 -m state --state RELATED,ESTABLISHED -j ACCEPT

自定义dnsmasq

这里再添加一个可以手工启动dnsmasq的小脚本

#!/bin/bash
brctl addbr routebr
ifconfig routebr 192.168.122.1 netmask 255.255.255.0
iptables -t nat -A POSTROUTING -s 192.168.122.0/24 -d ! 192.168.122.0/24 -j MASQUERADE
iptables -A FORWARD --destination 192.168.122.0/24 -m state --state RELATED,ESTABLISHED -j ACCEPT
/usr/sbin/dnsmasq \
--strict-order \
--bind-interfaces \
--pid-file=/usr/local/vps/network/default.pid \
--conf-file= \
--except-interface lo \
--listen-address 192.168.122.1 \
--dhcp-range 192.168.122.2,192.168.122.254 \
--dhcp-leasefile=/usr/local/vps/network/dnsmasq/default.leases \
--dhcp-lease-max=253 \
--dhcp-no-override \
--dhcp-hostsfile=/usr/local/vps/network/dnsmasq/default.hostsfile

重启network导致网络中断

当我们需要实时修改network的配置并使之生效的时候,就得重新启动此network,也就是需要net-destroy再net-start一下,我们的配置才能生效,但是随之而来的问题是,当network被重新启动之后,虚拟机便无法访问网络了,除非把虚拟机的network interface重新attach一下,或者等到虚拟机重新启动,那么为什么会出现这样的问题呢?我们先从它的表象开始分析,至于是否要追究到源码里面就取决于同学们自己了,反正我暂时没那功夫。这里仅仅是抛出来了一块砖。

当一个network启动之后,会自动生成一个虚拟网卡接口如virbr1,也会生成其他一些需要的东西,而重新启动了libvirt的network之后这个接口也会被重启,所以就导致了中途有一个中断的过程,

那事情就比较清晰了,如果你将libvirt启动网络的所有过程拆分开来一个一个的手动生成,需要修改某一部分配置的时候实际上你只需要修改对应的配置文件而不需要重新启动这个virbr1接口,比如上面提到的mac+ip的绑定,如果把dnsmasq独立出来,不让libvirt接管,那么增加了mac+ip绑定之后,仅仅需要重启dnsmasq这个服务。

正确使用shell返回值

· 阅读需 2 分钟

编写shell脚本的时候,正确使用返回值是运维人员的基本操守

1.常见返回值

下表列出了常见shell命令的退出返回值:

返回值含义示例说明
1各种常见错误let "var1 = 1/0"shell里面最常见的错误返回值
2shell内建功能使用错误empty_function() 常见于关键字或者命令出错
126命令无法执行/dev/null由于权限等导致的命令无法执行
127命令无法找到illegal_command一般是PATH环境变量不对等
128退出返回值错误exit 3.14159返回值只能是整数,小数就不对了
128+n信号 "n"+128kill -9 $PPID of script$? 即返回 137 (128 + 9)
130ctrl+c 退出Ctl-C其实ctrl+c返回的是2 (130 = 128 + 2)
255*返回值超出可接受的范围exit -1只能是 0 - 255

2. init标准返回值

下表列出了关于/etc/init.d/目录下启动控制脚本的标准返回值:

  • 0 程序在运行或者服务状态OK
  • 1 程序已经死掉,但是 pid文件仍在 /var/run目录下存在
  • 2 程序已经死掉,但是lock文件仍在 /var/lock 目录下存在
  • 3 程序没有运行
  • 4 程序运行状态未知
  • 5-99 供LSB扩展的保留段
  • 100-149 供特定系统发行版使用的保留段
  • 150-199 供特定程序使用的保留段
  • 200-254 保留段

3. 建议返回值

在写shell脚本的时候需要注意自定义的退出返回值最好不要与上面表格中所定义的重复,对于管理人员来说养成良好的习惯有助于遇到错误时作出正确的判断。 根据上表至少可以得出,在自定义返回值的时候:

  • 最好不要用的:0-4 126-130 255
  • 应避免使用的:5-99
  • 可随意使用的:100-125 131-254

参考文档:

Ovirt中的stateless实现机制分析

· 阅读需 9 分钟

我发现ovirt的node也就是运行虚拟机的主机被设计成了这样:整个根文件系统是只读的,只有部分配置文件被独立出来放到了另外的分区,问了几位IBM和红帽的工程师,明白了为什么需要这种stateless也就是无状态的设计

Ovirt简介

这是来自centos wiki中的描述:

oVirt 是个管理虚拟化的应用程序。言下之意就是你可利用 oVirt 的管理界面(oVirt 引擎)来管理硬件节点、存储及网络资源,并部署及监控在你的数据中心内运行的虚拟机器。 如果你熟识 VMware 的产器,oVirt 在理念上与 vSphere 类同。Red Hat 企业级虚拟化产品以 oVirt 作为基础,这个上游计划内开发的新功能,日后亦会在获支持的产品内出现。

ovirt是一套虚拟化管理的系统,其对标产品是vmware的vsphere,它分为ovirt-engine和ovirt-node,可以用ovirt的这套系统来管理虚拟机群,现在虚拟化相关产品众多,微软,vmware,oracle等都在做这方面的产品,于是IBM红帽ubuntu等厂商就联合起来开始搞这个东西了,红帽很早就开始投入kvm了,红帽做的是RHEV的整套系统,也分为node和engine只是名字不一样,后来干脆就把node的RHEV-H给贡献出来,大家一起搞ovirt了。前几天我也试用了一下ovirt和RHEV,发现他们的node也就是运行虚拟机的主机被设计成了这样: 整个根文件系统是只读的,只有部分配置文件被独立出来放到了另外的分区,问了几位IBM和红帽的工程师,才知道这叫stateless,无状态,这么做的好处是运行环境和存储分离,提高整体可用性。在分析了ovirt中的stateless实现机制之后,下面将在centos6上尝试手工配置,过程中请教了几位ovirt的开发,再次表示感谢

关于stateless

这种stateless的设计来自很早之前红帽在fedora里面做的尝试,目的是把系统做成liveCD,下面是一些关于stateless的描述:

read-only root file system(stateless linux)
Readonly root support.
This was add to Fedora for Stateless Linux, i.e. for creating live Fedora CDs.
How to use:
* Edit `/etc/sysconfig/readonly-root`. Set 'READONLY' to 'yes'.
* Add any exceptions that need to be writable that aren't in the stock `/etc/rwtab` to an /etc/rwtab.d file. (See below)
How it works:
* On boot, we mount a tmpfs (by default, at /var/lib/stateless/writable), and then parse `/etc/rwtab` and `/etc/rwtab.d/*` for things to put there.
These files have the format:
`<type> <path>`

* Types are as follows:
* empty: An empty path. Example:
`empty /tmp`
* dirs: A directory tree that is copied, empty. Example:
`dirs /var/run`
* files: A file or directory tree that is copied intact. Example:
`files /etc/resolv.conf`

A stock rwtab is shipped with common things that need mounted.
When your computer comes back up, the root and any other system
partitions will be mounted read-only. All the files and directories
listed in `/etc/rwtab` will be mounted read-write on a tmpfs filesystem.
You can add additional files and directories to rwtab to make them
writable after reboot.

Note that this system is stateless. When you reboot again, everything
written to the tmpfs filesystem vanishes and the system will be exactly
as it was the last time it was booted. You could add a writable
filesystem on disk or NFS for writing files you want to retain after
rebooting.

Take a look at `/etc/rc.d/rc.sysinit` to see how the magic is done.
This capability is a "technology preview" (beta) and is buggy. Note that
`/etc/mtab` and thus "mount" do not show the complete list of filesystems
because the /etc directory is on a read-only filesystem. /proc/mounts
always shows the correct mount information. You could update `/etc/mtab`
from /proc/mounts to correct it both after boot and after running the
mount or umount commands to change mounts.

Run `fgrep -v rootfs /proc/mounts >/etc/mtab` to correct `/etc/mtab`.
Note that mounting or symlinking /proc/mounts to /etc/mtab causes other
problems such as breaking the df command.

You can change your read-only root filesystem to read-write mode
immediately with this command run by the root user:
`mount -n -o remount,rw /`

分析其实现机制

下面把分析过程记录下来:

首先我看到的是fstab文件

/etc/fstab

/dev/root / ext2 defaults,ro,noatime 0 0
devpts /dev/pts devpts gid=5,mode=620 0 0
tmpfs /dev/shm tmpfs defaults 0 0
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
/dev/HostVG/Config /config ext4 defaults,noauto,noatime 0 0
debugfs /sys/kernel/debug debugfs 0 0
/dev/HostVG/Swap swap swap defaults 0 0
/dev/HostVG/Logging /var/log ext4 defaults,noatime 0 0
/dev/HostVG/Data /data ext4 defaults,noatime 0 0
/data/images /var/lib/libvirt/images bind bind 0 0
/data/core /var/log/core bind bind 0 0

这里要注意的地方就是root后面的ro,也就是说被挂载成了只读,这说明stateless是在挂载磁盘之前就已经完成,那肯定跟系统启动相关, 接着我们找到rc.sysinit这个在系统启动阶段执行的脚本中下面这段内容:

/etc/rc.d/rc.sysinit

for file in /etc/statetab /etc/statetab.d/* ; do
is_ignored_file "$file" && continue
[ ! -f "$file" ] && continue

if [ -f "$STATE_MOUNT/$file" ] ; then
mount -n --bind "$STATE_MOUNT/$file" "$file"
fi

for path in $(grep -v "^#" "$file" 2>/dev/null); do
mount_state "$path"
[ -n "$SELINUX_STATE" -a -e "$path" ] && restorecon -R "$path"
done
done

这段shell里面牵扯到了个/etc/statetab,而且通过对比普通系统还发现其他地方的不同

diff rc.sysinit /etc/rc.sysinit

102a103,108
> elif [[ "$system_release" =~ "CentOS" ]]; then
> [ "$BOOTUP" = "color" ] && echo -en "\\033[0;36m"
> echo -en "CentOS"
> [ "$BOOTUP" = "color" ] && echo -en "\\033[0;39m"
> PRODUCT=$(sed "s/CentOS \(.*\) \?release.*/\1/" /etc/system-release)
> echo " $PRODUCT"
499c505
< action $"Mounting local filesystems: " mount -a -t nonfs,nfs4,smbfs,ncpfs,cifs,gfs,gfs2,noproc,nosysfs,nodevpts -O no_netdev
---
> action $"Mounting local filesystems: " mount -a -t nonfs,nfs4,smbfs,ncpfs,cifs,gfs,gfs2 -O no_netdev
501c507
< action $"Mounting local filesystems: " mount -a -n -t nonfs,nfs4,smbfs,ncpfs,cifs,gfs,gfs2,noproc,nosysfs,nodevpts -O no_netdev
---
> action $"Mounting local filesystems: " mount -a -n -t nonfs,nfs4,smbfs,ncpfs,cifs,gfs,gfs2 -O no_netdev

接着看看这个/etc/statetab文件是个什么样子的:

/etc/statetab

# A list of paths which should be bind-mounted from a
# partition dedicated to persistent data
#
# See $STATE_LABEL in /etc/sysconfig/readonly-root
#
# Examples:
#
# /root
# /etc/ssh
# /var/spool/mail

顺藤摸瓜我们发现和/etc/sysconfig/readonly-root这个文件有关,从文件名上即可得知和只读的关联:

/etc/sysconfig/readonly-root

Set to 'yes' to mount the system filesystems read-only.
READONLY=yes
# Set to 'yes' to mount various temporary state as either tmpfs
# or on the block device labelled RW_LABEL. Implied by READONLY
TEMPORARY_STATE=no
# Place to put a tmpfs for temporary scratch writable space
RW_MOUNT=/var/lib/stateless/writable
# Label on local filesystem which can be used for temporary scratch space
RW_LABEL=stateless-rw
# Options to use for temporary mount
RW_OPTIONS=
# Label for partition with persistent data
STATE_LABEL=CONFIG
# Where to mount to the persistent data
STATE_MOUNT=/config
# Options to use for peristent mount
STATE_OPTIONS=
# NFS server to use for persistent data?
CLIENTSTATE=

就是这个了,首先它把READONLY设置为yes,然后使用设备的LABEL号来指定需要挂载为读写的设备,然后就是挂载的位置STATE_MOUNT

同时还有/etc/rwtab以及rwtab.d目录与这个有关: /etc/rwtab

#files /etc/adjtime
#files /etc/ntp.conf
#files /etc/resolv.conf
#files /etc/lvm/.cache
#files /etc/lvm/archive
#files /etc/lvm/backup

上面这几项在ovirt-node里是被注释掉了的,它使用自己的方式来变更这几个文件

/etc/rwtab.d/ovirt

files /etc
dirs /var/lib/multipath
dirs /var/lib/net-snmp
dirs /var/lib/dnsmasq
files /root/.ssh
dirs /root/.uml
dirs /root/.virt-manager
dirs /home/admin/.virt-manager
files /var/cache/libvirt
files /var/empty/sshd/etc/localtime
files /var/lib/libvirt
files /var/lib/multipath
files /var/cache/multipathd
empty /mnt
empty /live
files /boot
empty /boot-kdump
empty /cgroup

上面这些是ovirt自定义的。最后就是看/config下面的files文件:

/config/files

/etc/fstab
/etc/shadow
/etc/default/ovirt
/etc/ssh/ssh_host_key
/etc/ssh/ssh_host_key.pub
/etc/ssh/ssh_host_dsa_key
/etc/ssh/ssh_host_dsa_key.pub
/etc/ssh/ssh_host_rsa_key
/etc/ssh/ssh_host_rsa_key.pub
/etc/iscsi/initiatorname.iscsi
/etc/sysconfig/network-scripts/ifcfg-eth0
/etc/sysconfig/network-scripts/ifcfg-breth0
/etc/sysconfig/network-scripts/ifcfg-eth1
/etc/ntp.conf
/etc/sysconfig/network
/etc/hosts
/etc/shadow
/etc/ssh/sshd_config

此列表内的文件就是rc.sysinit需要读取的,把他们一个个挂载为读写,这样就实现了可修改配置文件白名单,于是我就动手修改了系统里的这些文件,重启,一切看似正常,但是当关机的时候新的问题出现了,关机的时候提示/etc无法被卸载,为什么呢,然后就找到与关机有关的脚本:

diff /etc/rc.d/init.d/halt.orig /etc/rc.d/init.d/halt

141c141
< LANG=C __umount_loop '$2 ~ /^\/$|^\/proc|^\/dev/{next}
---
> LANG=C __umount_loop '$2 ~ /^\/$|^\/proc|^\/etc|^\/dev/{next}

原来在halt文件里也做了“手脚”,把/etc给加了进去,重启,修改,关机,一切正常。

参考文档

下面是可以参考的文档