显示标签为“System Administrator”的博文。显示所有博文
显示标签为“System Administrator”的博文。显示所有博文

星期一, 五月 11, 2009

uuid of subversion

uuid of subversion

在做 subversion 迁移的时候,使用了 svnsync 来同步 repository 数据(编写的 svnsync.sh 脚本调用 svnadmin 和 svnsync init/sync 过程并日志),服务端的迁移没有什么问题,而客户端的重定位,直接使用 sw 不行:

  [root@vxy8_cfengine2 /tmp/cfengine.svn]
#svn sw http://172.22.35.10/svn/trunk/KS_CFENGINE/cfengine
svn: 'http://172.22.35.10/svn/trunk/KS_CFENGINE/cfengine'
is not the same repository as
'http://172.22.35.12/svn'

这就是说,直接使用 sw 需要在同一个主机的同一个 repository 下才行。

但 sw 有一个 --relocate 参数,可以切换 repository:

  [root@vxy8_cfengine2 /tmp/cfengine.svn]
#svn sw --relocate http://172.22.35.12/svn/trunk/KS_CFENGINE/cfengine http://172.22.35.10/svn/trunk/KS_CFENGINE/cfengine
svn: The repository at 'http://172.22.35.10/svn' has uuid '0ce980e2-6b68-4f57-841b-e7c3a84044d9', but the WC has '92f65df4-df84-11dc-ae99-89d8fa8a2030'

这个报错和 subversion 的 uuid 有关,这是由 svnsync 同步数据遗留的问题,源 repos 和新 repos 的 uuid 不一致,看看工作目录的情况:

  [root@vxy8_cfengine2 /tmp/cfengine.svn]
#svn info
Path: .
URL: http://172.22.35.12/svn/trunk/KS_CFENGINE/cfengine
Repository Root: http://172.22.35.12/svn
Repository UUID: 0ce980e2-6b68-4f57-841b-e7c3a84044d9
Revision: 2837
Node Kind: directory
Schedule: normal
Last Changed Author: roczhou.zhoup
Last Changed Rev: 2835
Last Changed Date: 2009-05-11 10:50:24 +0800 (Mon, 11 May 2009)

使用 svnlook uuid 可以查看 repos 的 uuid。下面是源 repos

  [root@vxy8_cfengine2 /tmp/cfengine.svn]
#svnlook uuid /home/admin/data/svn_repo/
0ce980e2-6b68-4f57-841b-e7c3a84044d9

下面是目标 repos

  [root@vxy7_cfengine1 /var/cfengine.svn]
#svnlook uuid /home/admin/data/svn_repo
92f65df4-df84-11dc-ae99-89d8fa8a2030

解决办法是,对于 subversion-1.5 以上,可直接在运行 svnadmin setuuid 来设置源或目标 repos 的 uuid 使其保持一致,对低于 subversion-1.5 的系统,示例如下(更改源 repos):

  [root@vxy8_cfengine2 /tmp/cfengine.svn]
#vi /tmp/x

SVN-fs-dump-format-version: 2

UUID: 92f65df4-df84-11dc-ae99-89d8fa8a2030

[root@vxy8_cfengine2 /tmp/cfengine.svn]
#cat /tmp/x | svnadmin load --force-uuid /home/admin/data/svn_repo/

#svnlook uuid /home/admin/data/svn_repo/
92f65df4-df84-11dc-ae99-89d8fa8a2030

[root@vxy8_cfengine2 /tmp/cfengine.svn]
#svn info
Path: .
URL: http://172.22.35.12/svn/trunk/KS_CFENGINE/cfengine
Repository Root: http://172.22.35.12/svn
Repository UUID: 0ce980e2-6b68-4f57-841b-e7c3a84044d9
Revision: 2837
Node Kind: directory
Schedule: normal
Last Changed Author: roczhou.zhoup
Last Changed Rev: 2835
Last Changed Date: 2009-05-11 10:50:24 +0800 (Mon, 11 May 2009)

# 此时还没变

[root@vxy8_cfengine2 /tmp/cfengine.svn]
#svn sw --relocate http://172.22.35.12/svn/trunk/KS_CFENGINE/cfengine http://172.22.35.10/svn/trunk/KS_CFENGINE/cfengine
svn: The repository at 'http://172.22.35.10/svn' has uuid '51c9629f-21a9-4eaa-825b-20ccd5c61100', but the WC has '92f65df4-df84-11dc-ae99-89d8fa8a2030'

[root@vxy8_cfengine2 /tmp/cfengine.svn]
#svn up

[root@vxy8_cfengine2 /tmp/cfengine.svn]
#svn info
Path: .
URL: http://172.22.35.12/svn/trunk/KS_CFENGINE/cfengine
Repository Root: http://172.22.35.12/svn
Repository UUID: 92f65df4-df84-11dc-ae99-89d8fa8a2030
Revision: 2837
Node Kind: directory
Schedule: normal
Last Changed Author: roczhou.zhoup
Last Changed Rev: 2835
Last Changed Date: 2009-05-11 10:50:24 +0800 (Mon, 11 May 2009)

# 此时已经改变

[root@vxy8_cfengine2 /tmp/cfengine.svn]
#svn sw --relocate http://172.22.35.12/svn/trunk/KS_CFENGINE/cfengine http://172.22.35.10/svn/trunk/KS_CFENGINE/cfengine
svn: The repository at 'http://172.22.35.10/svn' has uuid '51c9629f-21a9-4eaa-825b-20ccd5c61100', but the WC has '92f65df4-df84-11dc-ae99-89d8fa8a2030'

但运行还是出错,这说明,更改源 repos 的 uuid 无效!

那么尝试更改目标的 uuid(下面让 vxy8_cfengine2 作为目标,vxy7_cfengine1 作为源)

  [root@vxy8_cfengine2 /tmp/cfengine.svn]
#!286
cat /tmp/y | svnadmin load --force-uuid /home/admin/data/svn_repo/

[root@vxy8_cfengine2 /tmp/cfengine.svn]
#svnlook uuid /home/admin/data/svn_repo/
0ce980e2-6b68-4f57-841b-e7c3a84044d9

[root@vxy7_cfengine1 /tmp/cfengine.svn]
#svn info
Path: .
URL: http://172.22.35.10/svn/trunk/KS_CFENGINE/cfengine
Repository Root: http://172.22.35.10/svn
Repository UUID: 92f65df4-df84-11dc-ae99-89d8fa8a2030
Revision: 2842
Node Kind: directory
Schedule: normal
Last Changed Author: roczhou.zhoup
Last Changed Rev: 2842
Last Changed Date: 2009-05-11 14:52:36 +0800 (Mon, 11 May 2009)

[root@vxy7_cfengine1 /tmp/cfengine.svn]
#svn sw --relocate http://172.22.35.10/svn/trunk/KS_CFENGINE/cfengine http://172.22.35.12/svn/trunk/KS_CFENGINE/cfengine
svn: The repository at 'http://172.22.35.12/svn' has uuid '92f65df4-df84-11dc-ae99-89d8fa8a2030', but the WC has '0ce980e2-6b68-4f57-841b-e7c3a84044d9'

[root@vxy8_cfengine2 /tmp/cfengine.svn]
#cat /tmp/x | svnadmin load --force-uuid /home/admin/data/svn_repo/

[root@vxy8_cfengine2 /tmp/cfengine.svn]
#svnlook uuid /home/admin/data/svn_repo/
92f65df4-df84-11dc-ae99-89d8fa8a2030

[root@vxy7_cfengine1 /tmp/cfengine.svn]
#svn sw --relocate http://172.22.35.10/svn/trunk/KS_CFENGINE/cfengine http://172.22.35.12/svn/trunk/KS_CFENGINE/cfengine

[root@vxy7_cfengine1 /tmp/cfengine.svn]
#svn info
Path: .
URL: http://172.22.35.12/svn/trunk/KS_CFENGINE/cfengine
Repository Root: http://172.22.35.12/svn
Repository UUID: 92f65df4-df84-11dc-ae99-89d8fa8a2030
Revision: 2848
Node Kind: directory
Schedule: normal
Last Changed Author: roczhou.zhoup
Last Changed Rev: 2847
Last Changed Date: 2009-05-11 16:15:01 +0800 (Mon, 11 May 2009)

这里已经完成了切换。所以,要统一 uuid,需要更改目标 repos 的 uuid。

另外,从上面的例子可以看出,修改了 repos 的 uuid 之后,对于已经检查(checkout/co)的工作目录不会有影响,当其下一次 svn up 之后,其 info 中的 uuid 即进行了更新。

星期五, 十二月 26, 2008

自动化文档管理方案

基本思路

  1. 使用简单的 t2t 标记进行文档编写
  2. 使用 subversion 对这些文档进行版本控制
  3. 使用 GNU Make 实现自动化管理
  4. 文档编写之后使用 txt2tags 进行文档转换为其它格式(通过调用相应 Makefile target 实现)
  5. 使用 mutt/msmtp 自动发送转换后文本到某个邮件列表(相应 make target)
  6. 自动同步到在线文档系统?
  7. 自动作图?(目前可用 dia)
  8. 上传图片?
  9. 对项目,自动生成站点层级页面?

目前已实现前面五点,后续功能方案研究中...

我使用的系统环境为

  $ uname
CYGWIN_NT-5.1

所以任何 Linux/UNIX 系统都是合适的。

略去部分

  • txt2tags 比较简单,参考官方文档大概 <20min>
  • subversion 使用广泛,在此也不赘述

mutt/msmtp

mutt/msmtp 在 Cygwin 似乎不太稳定,但基本可以使用:

  mutt-1.4.2.2-2
msmtp-1.4.13-1

mutt 是 MUA,它需要一个 MTA 来为它发送邮件,默认情况下它会使用 sendmail 或 postfix 的 sendmail 命令,但安装和配置一个 sendmail/postfix 太麻烦了,对于这种小应用不合适,所以使用 msmtp,它是一个轻量级的 MTA。

因为只需要在命令行调用 mutt,所以不需要进行太复杂的设置,编写 mutt 和 msmtp 相应的配置文件如下:

  sh$ cat ~/.mutt/private.muttrc
# SMTP
set sendmail="/usr/bin/msmtp -f someone@gmail.com"

sh$ cat ~/.msmtprc
account private
host smtp.gmail.com
port 587
protocol smtp
auth on
from someone@gmail.com
user someone@gmail.com
password "********"
tls on
tls_starttls on
tls_certcheck off

~/.mutt/private.muttrc 指明了使用 msmtp 及其参数,-f 即 .msmtprc 中的 from 内容,用这个来标识要使用哪个账号来发送邮件,因为我们可能要使用多个账号发不同的邮件,比如对工作的内容要使用另一个账号,这也是为什么没有使用标准的 ~/.muttrc 或 ~/.mutt/muttrc 作为 mutt 配置的原因,下面会将到如何使用其他账号。

可以先尝试一下是否发送会成功:

  sh$ echo "testing mutt..." | mutt -s "Mutt" -F ~/.mutt/private.muttrc $mail_address

到另一个邮箱 $mail_address 看看是否确实收到了邮件。

将工作时使用的邮箱加入 ~/.msmtprc 后如下:

  $ cat ~/.msmtprc
account private
host smtp.gmail.com
port 587
protocol smtp
auth on
from someone@gmail.com
user someone@gmail.com
password "********"
tls on
tls_starttls on
tls_certcheck off

account default
host ssl.alibaba-inc.com
port 465
protocol smtp
auth on
from someone@company.com
user someone
password "********"
tls on
tls_starttls off
tls_certcheck off
# tls_force_sslv3 on

注意公司的邮箱使用 ironport,参数 tls_starttls off 与 gmail 邮箱的不同,为了使用这个设置,需要另一个 mutt 配置文件:

  sh$ cat ~/.mutt/work.muttrc
# Header
my_hdr From: someone@company.com
# SMTP
set sendmail="/usr/bin/msmtp -f someone@company.com"

因为公司账号没有域名后缀,所以发出去的邮件 header 部分将没有域名部分,故在 ~/.mutt/work.muttrc 中增加 my_hdr 让 mutt 帮补上。

可再用前述方法试验一下是否能够正确发送邮件。

Makefile

邮件客户端配置成功后,就可以在文档目录下编写一个 Makefile。此时正确的目录结构很重要,方便我们进行管理:

  docs/
index.t2t, 用来生成结构化文档
*.t2t, 生成单独文档到 html/ 和 text/ 下
html/*.html, 转换后的 html 文件
text/*.txt, 转换后的 txt文件
_mail/work, 使用 work 邮箱时的时间戳文件
_mail/private, 使用私有邮箱时的时间戳文件
_release, 发布文档列表,只有在这个列表中的文件发生变更后才会发送邮件和进行在线同步
Makefile -> ../Makefile, 可制成符号链接到父目录 Makefile,这样可以用同一个 Makefile 管理大量分类文档

Makefile 内容如下:

  SA_MAIL := sa@list.company.com

html: *.t2t
for f in $?; do fn=`echo $$f | awk -F. '{print $$1}'` && txt2tags -t html -o html/$$fn.html $$f; done

txt: *.t2t
for f in $?; do fn=`echo $$f | awk -F. '{print $$1}'` && txt2tags -t txt -o text/$$fn.txt $$f; done

_mail/work: *.t2t
for f in $?; do \
grep $$f _release >/dev/null && \
( \
echo "mail $$f ..."; \
fn=`echo $$f | awk -F. '{print $$1}'`; \
title=`sed -n '1p' $$f`; \
cat text/$$fn.txt | mutt -F ~/.mutt/work.muttrc -s "$$title" -a html/$$fn.html $(SA_MAIL); \
) || \
echo "skip $$f ..."; \
done
touch _mail/work

当运行make htmlmake txt这两个 target 时就会分别在 html/ 或 text/ 下生成转换文档,运行make _mail/work后会用工作邮箱发送邮件到邮件列表 SA_MAIL,邮件内容为生成 txt 内容,附件为生成 html 文件。

星期二, 十二月 18, 2007

postfix inet_interfaces 问题

一个报警脚本,发邮件出现问题,重定向输出中包含如下报错:
send-mail: fatal: parameter inet_interfaces: no local interface found for 124.74.193.221
,/var/log/maillog 中的记录为:
Dec 16 12:09:24 www2 postfix/pickup[30784]: fatal: parameter inet_interfaces: no local interface found for 124.74.193.221
Dec 16 12:09:25 www2 postfix/master[23234]: warning: process /usr/libexec/postfix/pickup pid 30784 exit status 1
Dec 16 12:09:25 www2 postfix/master[23234]: warning: /usr/libexec/postfix/pickup: bad command startup -- throttling
重启 postfix,即 /etc/init.d/postfix restart 失败,/var/log/maillog 中的输出为:
Dec 18 10:28:16 www2 postfix/master[23234]: terminating on signal 15
Dec 18 10:28:17 www2 postfix/sendmail[11621]: fatal: parameter inet_interfaces: no local interface found for 124.74.193.221
Dec 18 10:28:25 www2 postfix[11627]: fatal: parameter inet_interfaces: no local interface found for 124.74.193.221
Dec 18 10:28:26 www2 postfix/sendmail[11629]: fatal: parameter inet_interfaces: no local interface found for 124.74.193.221
Dec 18 10:28:27 www2 postfix[11630]: fatal: parameter inet_interfaces: no local interface found for 124.74.193.221
Dec 18 10:28:29 www2 postfix/sendmail[11632]: fatal: parameter inet_interfaces: no local interface found for 124.74.193.221
但是这台主机的 IP 地址是 124.74.193.211,为什么会出现 221 呢?

因为前两天因为发邮件,将原来指向该主机的一个域名重新定向到了 124.74.193.221,在 /etc/postfix/main.cf 中的配置为:
myhostname = mail.groups.shopex.cn
inet_interfaces = $myhostname, localhost, 124.74.193.211, 192.168.0.211
而此时 mail.groups.shopex.cn 已经解析到 124.74.193.221,这样 postfix 会先做解析,结果就不对了。

postfix inet_interfaces 有参数可以指定不使用 DNS,应该是加 [] 的方式。

星期三, 十一月 07, 2007

dig DNS 迭代查询以及双线 view 问题

默认情况下,DNS 都会使用递归查询。通过迭代查询,可以获得查询的路径,即对域名是如何被解析的得到一个直观印象。可以通过运行
sh$ dig +trace www.example.com (@server)
来进行查询。

我在 "变态"DNS中曾经讨论过为双线设置 DNS 的方法。但最近发现老是会被解析到网通的服务器,即使是电信的 DNS 服务器,也会得到网通的结果。用 dig +trace 也看不出所以然来。

后来发现网通的 slave DNS 的两个域文件(电信 .zone 和 网通 .cnc_zone)的内容完全一样。原来从服务器同步的时候,因为地址是网通的,所以主服务器只会返回网通的结果。这样看来,从主服务器之间也是通过 53 端口传递数据并且也受 acl view 规则的影响。

要解决这个问题,复杂一点的办法是从服务器绑定两个 IP 地址,主服务器的两个 view 设置不同的 allow-transfer {},从服务器的两个 view 设置两个 transfer-source $ipaddr,可参考:
http://www.chinalinuxpub.com/read.php?wid=1452

简单点的办法就是把两个都设置成主的 DNS 并手工同步。

星期五, 十一月 02, 2007

SNMP access 的一个问题

SNMP Agent 的配置文件:
sh$ egrep -v '(^#|^$)' snmpd.conf
syslocation Unknown (edit /etc/snmp/snmpd.conf)
syscontact Root (configure /etc/snmp/snmp.local.conf)
pass .1.3.6.1.4.1.4413.4.1 /usr/bin/ucd5820stat
syscontact monitor@zovatech.com
disk / 36G
com2sec mynet 192.168.0.0/24 public
com2sec mynet 222.66.231.106 public
group mynet v1 mynet
group mynet v2c mynet
view system included .1
access mynet "" any noauth exact system none none
exec .1.3.6.1.4.1.2021.54 hdNum /usr/local/bin/snmpdiskio hdNum
exec .1.3.6.1.4.1.2021.55 hdIndex /usr/local/bin/snmpdiskio hdIndex
exec .1.3.6.1.4.1.2021.56 hdDescr /usr/local/bin/snmpdiskio hdDescr
exec .1.3.6.1.4.1.2021.57 hdInBlocks /usr/local/bin/snmpdiskio hdInBlocks
exec .1.3.6.1.4.1.2021.58 hdOutBlocks /usr/local/bin/snmpdiskio hdOutBlocks
本来没有设置 com2sec mynet 222.66.231.106 的。因为 SNMP client 这台主机有两个 IP,一个是内网的 192.168.0.1,还有一个是外网的 202.66.231.106,而 SNMP Agent 所在主机为 192.168.0.197。则在配置 SNMP access 的候,一个很直接的想法就是只要允许了所有内网的主机就应该可以了呀,但是在这里却不行,对于 192.168.0.1 这台主机,必须要使用外网(eth1)的那个 IP 来指定。

不知道原因是什么?

星期三, 十月 31, 2007

"Permission Denied" when load php module (SELinux)

Cannot load /usr/local/apache2/modules/libphp4.so into server: /usr/local/apache2/modules/libphp4.so : cannot restore segment prot after reloc: Permission Denied
这个错误是因为打开了 RedHat 的 SELinux,关闭就可以了。

另外,在编译 php 时使用了 --enable-gd-native-ttf 和 --with-gd=/usr/local 参数,待同样报告类似的错误:
/usr/local/lib/libgd.so.2: cannot restore segment prot after reloc: Permission Denied
,同样也是 SELinux 造成的。

星期六, 十月 13, 2007

bind slave 同步中的一个问题

昨天发现辅 DNS 同步有问题,一些后来加入的域名没有被传输,重启主、辅服务器都没有用,但在 /var/log/messages 里面也没有显示报错信息。

主、辅服务器都是使用的数据 zone 文件,于是我在辅助服务器上删除了原来的数据文件,再重启辅助服务器,发现这次新的域名被同步过来了!

星期二, 十月 09, 2007

数据库连接与 /etc/hosts 设置

上次数据库出问题后更换了主机,但之后时常出现"Can not connect to database"的错误,考虑了若干中可能,但最有效的还是先查看系统日志 /var/log/message:
Oct  9 10:49:31 shopex mysqld: gethostby*.getanswer: asked for "210.0.168.192.in-addr.arpa IN PTR", got type "A"
...
Oct 9 10:49:32 shopex mysqld: gethostby*.getanswer: asked for "210.0.168.192.in-addr.arpa IN PTR", got type "A"
这说明是地址解析的问题,mysqld 中设置了反向 DNS 解析。一般只需要在 /etc/hosts 中增加相应的记录即可:
192.168.0.210   www

星期五, 九月 28, 2007

trouble shooting for ext3fs with disk error

今天放数据库的主机不能正常运行了,因为以前出过文件系统错误,当时我把数据磁盘 umount,再做 fsck.ext3 /dev/sdb1 后就直接从 journal 恢复了。但今天却不行,当运行 fsck.ext3 /dev/sdb1 的时候,出现
(Attempt to read block from filesystem resulted in short read).  Ignore error?
这样的错误。

于是检查一下系统日志 /var/log/message
Sep 23 04:07:38 db kernel: EXT3-fs error (device sdb1) in start_transaction: Journal has aborted
Sep 23 04:09:31 db kernel: EXT3-fs error (device sdb1) in start_transaction: Journal has aborted
Sep 23 04:28:51 db kernel: EXT3-fs error (device sdb1) in start_transaction: Journal has aborted
Sep 23 04:31:38 db kernel: EXT3-fs error (device sdb1) in start_transaction: Journal has aborted
Sep 23 04:34:01 db kernel: EXT3-fs error (device sdb1) in start_transaction: Journal has aborted
Sep 23 04:37:40 db kernel: EXT3-fs error (device sdb1) in start_transaction: Journal has aborted
......
Sep 23 12:07:13 db kernel: EXT3-fs error (device sdb1) in start_transaction: Journal has aborted
Sep 23 12:07:36 db kernel: EXT3-fs error (device sdb1) in start_transaction: Journal has aborted
Sep 23 12:40:05 db kernel: __journal_remove_journal_head: freeing b_frozen_data
Sep 23 12:40:05 db kernel: __journal_remove_journal_head: freeing b_committed_data
Sep 23 12:40:05 db kernel: __journal_remove_journal_head: freeing b_frozen_data
Sep 23 12:40:59 db kernel: kjournald starting. Commit interval 5 seconds
Sep 23 12:40:59 db kernel: EXT3 FS on sdb1, internal journal
Sep 23 12:40:59 db kernel: EXT3-fs: mounted filesystem with ordered data mode.
Sep 27 02:34:00 db kernel: (scsi1:A:1:0): Unexpected busfree in Data-in phase
Sep 27 02:34:00 db kernel: SEQADDR == 0x82
Sep 27 02:34:00 db kernel: SCSI error : <1 0 1 0> return code = 0x10000
Sep 27 02:34:00 db kernel: end_request: I/O error, dev sdb, sector 272737767
Sep 27 02:34:01 db kernel: SCSI error : <1 0 1 0> return code = 0x10000
Sep 27 02:34:01 db kernel: end_request: I/O error, dev sdb, sector 272737775
Sep 27 02:34:01 db kernel: SCSI error : <1 0 1 0> return code = 0x10000
Sep 27 02:34:01 db kernel: end_request: I/O error, dev sdb, sector 272737783
Sep 27 02:34:01 db kernel: SCSI error : <1 0 1 0> return code = 0x10000
Sep 27 02:34:01 db kernel: end_request: I/O error, dev sdb, sector 272737791
Sep 27 02:34:01 db kernel: SCSI error : <1 0 1 0> return code = 0x10000
Sep 27 02:34:01 db kernel: end_request: I/O error, dev sdb, sector 272737799
Sep 27 02:34:02 db kernel: SCSI error : <1 0 1 0> return code = 0x10000
......
Sep 27 02:34:14 db kernel: SCSI error : <1 0 1 0> return code = 0x8000002
Sep 27 02:34:14 db kernel: Info fld=0x0, Current sdb: sense key Aborted Command
Sep 27 02:34:14 db kernel: end_request: I/O error, dev sdb, sector 272737767
Sep 27 02:34:14 db kernel: (scsi1:A:1): 160.000MB/s transfers (80.000MHz DT, offset 127, 16bit)
Sep 27 02:34:15 db kernel: (scsi1:A:1:0): Unexpected busfree in Data-in phase
Sep 27 02:34:15 db kernel: SEQADDR == 0x56
Sep 27 02:34:15 db kernel: SCSI error : <1 0 1 0> return code = 0x10000
Sep 27 02:34:15 db kernel: end_request: I/O error, dev sdb, sector 272872423
Sep 27 02:34:16 db kernel: SCSI error : <1 0 1 0> return code = 0x10000
Sep 27 02:34:16 db kernel: end_request: I/O error, dev sdb, sector 272872431
......
Sep 27 02:34:30 db kernel: SCSI error : <1 0 1 0> return code = 0x8000002
Sep 27 02:34:30 db kernel: Info fld=0x0, Current sdb: sense key Aborted Command
Sep 27 02:34:30 db kernel: end_request: I/O error, dev sdb, sector 272872639
Sep 27 02:34:30 db kernel: (scsi1:A:1): 160.000MB/s transfers (80.000MHz DT, offset 127, 16bit)
Sep 28 02:35:10 db kernel: (scsi1:A:1:0): Unexpected busfree in Data-in phase
Sep 28 02:35:10 db kernel: SEQADDR == 0x53
Sep 28 02:35:10 db kernel: scsi1:A:15: parity error detected while idle. SEQADDR(0x1) SCSIRATE(0xc2)
Sep 28 02:35:10 db kernel: No terminal CRC packet recevied
Sep 28 02:35:10 db kernel: SCSI error : <1 0 1 0> return code = 0x10000
Sep 28 02:35:10 db kernel: end_request: I/O error, dev sdb, sector 270637663
......
Sep 28 02:35:27 db kernel: SCSI error : <1 0 1 0> return code = 0x8000002
Sep 28 02:35:27 db kernel: Info fld=0x0, Current sdb: sense key Aborted Command
Sep 28 02:35:27 db kernel: end_request: I/O error, dev sdb, sector 120062031
Sep 28 02:35:27 db kernel: EXT3-fs error (device sdb1): ext3_get_inode_loc: unable to read inode block - inode=7503878, block=15007746
Sep 28 02:35:27 db kernel: Aborting journal on device sdb1.
Sep 28 02:35:27 db kernel: SCSI error : <1 0 1 0> return code = 0x8000002
Sep 28 02:35:27 db kernel: Info fld=0x0, Current sdb: sense key Aborted Command
Sep 28 02:35:27 db kernel: end_request: I/O error, dev sdb, sector 273146143
......
Sep 28 02:35:29 db kernel: SCSI error : <1 0 1 0> return code = 0x8000002
Sep 28 02:35:29 db kernel: Info fld=0x0, Current sdb: sense key Aborted Command
Sep 28 02:35:29 db kernel: end_request: I/O error, dev sdb, sector 63
Sep 28 02:35:29 db kernel: EXT3-fs error (device sdb1) in ext3_reserve_inode_write: IO failure
Sep 28 02:35:29 db kernel: SCSI error : <1 0 1 0> return code = 0x8000002
Sep 28 02:35:29 db kernel: Info fld=0x0, Current sdb: sense key Aborted Command
Sep 28 02:35:29 db kernel: end_request: I/O error, dev sdb, sector 272573927
Sep 28 02:35:29 db kernel: SCSI error : <1 0 1 0> return code = 0x8000002
Sep 28 02:35:29 db kernel: Info fld=0x0, Current sdb: sense key Aborted Command
Sep 28 02:35:29 db kernel: end_request: I/O error, dev sdb, sector 272574783
Sep 28 02:35:29 db kernel: ext3_abort called.
Sep 28 02:35:29 db kernel: EXT3-fs error (device sdb1): ext3_journal_start_sb: Detected aborted journal
Sep 28 02:35:29 db kernel: Remounting filesystem read-only
Sep 28 02:35:29 db kernel: EXT3-fs error (device sdb1) in start_transaction: Journal has aborted
Sep 28 02:35:29 db kernel: EXT3-fs error (device sdb1) in start_transaction: Journal has aborted
Sep 28 02:35:29 db kernel: (scsi1:A:1): 160.000MB/s transfers (80.000MHz DT, offset 127, 16bit)
Sep 28 02:35:29 db kernel: EXT3-fs error (device sdb1) in ext3_dirty_inode: IO failure
Sep 28 02:35:29 db kernel: EXT3-fs error (device sdb1) in start_transaction: Journal has aborted
Sep 28 02:35:29 db kernel: __journal_remove_journal_head: freeing b_frozen_data
Sep 28 02:35:29 db kernel: __journal_remove_journal_head: freeing b_frozen_data
Sep 28 02:35:55 db kernel: EXT3-fs error (device sdb1) in start_transaction: Journal has aborted
Sep 28 02:41:25 db kernel: EXT3-fs error (device sdb1) in start_transaction: Journal has aborted
Sep 28 03:10:40 db kernel: EXT3-fs error (device sdb1) in start_transaction: Journal has aborted
Sep 28 03:42:08 db kernel: EXT3-fs error (device sdb1) in start_transaction: Journal has aborted
Sep 28 03:51:37 db kernel: EXT3-fs error (device sdb1) in start_transaction: Journal has aborted
Sep 28 03:54:38 db kernel: EXT3-fs error (device sdb1) in start_transaction: Journal has aborted
......
基本上,可以肯定是磁盘损坏了,出现了坏道!

因为这里是没有使用 RAID 设施的,所以只能使用 rescue 主机替代,这属于高可用(HA: High Availability)的问题了,这里不讨论。回过头来,需要检查和确认这块磁盘的问题。

首先使用另外一个 IP 启动这台主机(因为 HA 已经使用了原来的 IP 地址)。在这里我遇到的情况是在重启时第二块磁盘(/dev/sdb)还是被“正常”挂载了(/dev/sdb1),这意味着你仍然可以从里面把数据拷贝出来,当然最好是只读使用!——运气不错,不是吗?但这并不意味着这块磁盘就没有问题了,需要先进行检查:
sh# fsck.ext3 /dev/sdb1
e2fsck 1.35 (28-Feb-2004)
/dev/sdb1: clean, 39195/17924096 files, 22706784/35843015 blocks
好像没有问题?但真的是这样吗?

应该使用其他的超级块检查一下,因为很可能这个超级块本身就有问题。从 Ext3 文件系统使用磁盘的基本原理上看,一个磁盘被分成若干 groups,每个 groups 包含很多物理 blocks(通常为 4KB,和内存的一个 page 的大小一致。注意,这里和使用 fdisk 是显示的 Blocks 是不一样的,后者是以 1KB 为单元的),通常一个 group 可能包含 8192/16384/32768 个 blocks,取决于磁盘的大小或创建分区文件系统时的设置,而每一个 group 上面都有一个超级块(super block),只有第一个 group 的超级块被实际使用,其他 group 的超级块都作为备用。因此,尝试使用另外一个超级块:
sh# fsck.ext3 -b 8192 /dev/sdb1
e2fsck 1.35 (28-Feb-2004)
fsck.ext3: Bad magic number in super-block while trying to open /dev/sdb1

The superblock could not be read or does not describe a correct ext2
filesystem. If the device is valid and it really contains an ext2
filesystem (and not swap or ufs or something else), then the superblock
is corrupt, and you might try running e2fsck with an alternate superblock:
e2fsck -b 8193

sh# fsck.ext3 -b 8193 /dev/sdb1
e2fsck 1.35 (28-Feb-2004)
fsck.ext3: Bad magic number in super-block while trying to open /dev/sdb1

The superblock could not be read or does not describe a correct ext2
filesystem. If the device is valid and it really contains an ext2
filesystem (and not swap or ufs or something else), then the superblock
is corrupt, and you might try running e2fsck with an alternate superblock:
e2fsck -b 8193
两者都不行!这说明一个 group 的大小不是 8192,第二个 super block 不在第 8193 个 block 上,那么可以通过 tune2fs 来获得文件系统的信息,进而找到下一个超级块。
sh# tune2fs -l /dev/sdb1
tune2fs 1.35 (28-Feb-2004)
Filesystem volume name:
Last mounted on:
Filesystem UUID: 204a2d09-12d5-4623-bb0d-75b2e340401d
Filesystem magic number: 0xEF53
Filesystem revision #: 1 (dynamic)
Filesystem features: has_journal resize_inode filetype sparse_super large_file
Default mount options: (none)
Filesystem state: clean
Errors behavior: Continue
Filesystem OS type: Linux
Inode count: 17924096
Block count: 35843015
Reserved block count: 1792150
Free blocks: 13136231
Free inodes: 17884901
First block: 0
Block size: 4096
Fragment size: 4096
Reserved GDT blocks: 1024
Blocks per group: 32768
Fragments per group: 32768
Inodes per group: 16384
Inode blocks per group: 512
Filesystem created: Sun Jan 7 15:50:06 2007
Last mount time: Fri Sep 28 10:05:06 2007
Last write time: Fri Sep 28 11:01:56 2007
Mount count: 1
Maximum mount count: 35
Last checked: Fri Sep 28 10:05:05 2007
Check interval: 15552000 (6 months)
Next check after: Wed Mar 26 10:05:05 2008
Reserved blocks uid: 0 (user root)
Reserved blocks gid: 0 (group root)
First inode: 11
Inode size: 128
Journal inode: 8
Default directory hash: tea
Directory Hash Seed: dd1084ed-a28f-489e-8e47-c4a3a49f5e6d
Journal backup: inode blocks
可以看出每个块组拥有 32768 个 blocks,所以应该指定 -b 32768 来进行检查:
sh# fsck.ext3 -b 32768 /dev/sdb1
e2fsck 1.35 (28-Feb-2004)
/dev/sdb1 contains a file system with errors, check forced.
Pass 1: Checking inodes, blocks, and sizes
Duplicate blocks found... invoking duplicate block passes.
Pass 1B: Rescan for duplicate/bad blocks
Duplicate/bad block(s) in inode 311389: 712136
Duplicate/bad block(s) in inode 311425: 680600 680633 680634 680635 680636 680637 680638 680639 680641 680642 680643 680644 680645 680646 680647 680665 680666 680667 680668 680669 680670 680671 680673 681752 682704 684104 684352 685688 686064
Duplicate/bad block(s) in inode 311560: 657856 658872 662192 665192 669600 686504 689824 694104 696144 696904 698816 703032
Duplicate/bad block(s) in inode 344144: 705776 706088 706784
Duplicate/bad block(s) in inode 344151: 708952
Duplicate/bad block(s) in inode 344170: 709200
Duplicate/bad block(s) in inode 377354: 810496
Error reading block 22872266 (Attempt to read block from filesystem resulted in short read). Ignore error?
已经报告出现了 bad blocks。

short read 通常意味着文件系统中的一个 i 节点指向一个不能再读取的块,或者关于文件系统的某个元数据位于一个或者几个不能读取的块。对于日志文件系统,如果文件系统的日志的任何一部分存储在一个坏块上,也会出现这个错误,因为不能读取事务。

如果运气不够好,磁盘分区完全不能挂载和读取,可以先尝试把日志删除,再尝试挂接和访问数据。先用 debugfs 来看看文件系统的特性:
sh# debugfs /dev/sdb1
debugfs 1.35 (28-Feb-2004)
debugfs: features
Filesystem features: has_journal resize_inode filetype sparse_super large_file
debugfs: open /dev/sdb1
open: Filesystem /dev/sdb1 is still open. Close it first.
debugfs: open /dev/sdb1
open: Filesystem /dev/sdb1 is still open. Close it first.
debugfs: close /dev/sdb1
close: Usage: close_filesys
debugfs:
debugfs: open /dev/sdb1
open: Filesystem /dev/sdb1 is still open. Close it first.
debugfs: quit
可以看到 has_journal 特性。使用 tune2fs 可以设置和清除文件系统特性。下面的命令删除日志:
sh# tune2fs -f -O ^has_journal /dev/sdb1
tune2fs 1.35 (28-Feb-2004)
sh# debugfs /dev/sdb1
debugfs 1.35 (28-Feb-2004)
debugfs: features
Filesystem features: resize_inode filetype sparse_super large_file
debugfs: open /dev/sdb1
open: Filesystem /dev/sdb1 is still open. Close it first.
debugfs: quit
has_journal 特性已经被清除。这时候,再次运行 fsck.ext3,不使用 -b 指定 super block,也可以看到报错信息:
e2fsck 1.35 (28-Feb-2004)
/dev/sdb1 contains a file system with errors, check forced.
Pass 1: Checking inodes, blocks, and sizes
Error reading block 35750122 (Attempt to read block from filesystem resulted in short read) while doing inode scan. Ignore error?
总之,要从一个损坏的磁盘上恢复数据总是没有保证的,所以备份至关重要——"热镜像 + 轮转拷贝"才是根本之道。

参考:《LINUX SERVER HACKS 卷二》, Hacks #94 "从崩溃的磁盘恢复数据"。

另外,我的项目 cutils包含了 mirrord/fs_mirror 镜像和轮转工具。

星期四, 九月 20, 2007

RHEL5 部分内存无法使用?

因为装 RAID1 + LVM,使用了一块技嘉的板子,nvidia GeForce 6100/nForce 430 芯片,结果原来的 RHEL4 没有它的 SATA 驱动,下载的 sata_nv 驱动也始终装不上,只好换用了 RHEL5,很顺利的安装成功了。

但进入系统后,使用 free 发现 4G 的内存,却只显示 3.4GB 左右。在 BIOS 中对于显卡的 frame buffer 的设置最大也只有 256MB,不可能差这边大。

从 dmesg 的记录来看,在 Linux 启动的时候有一个 Warning 信息,
 BIOS-e820: 00000000f0000000 - 00000000f2000000 (reserved)
BIOS-e820: 00000000fec00000 - 0000000100000000 (reserved)
BIOS-e820: 0000000100000000 - 0000000120000000 (usable)
Warning only 4GB will be used.
Use a PAE enabled kernel.
3200MB HIGHMEM available.
896MB LOWMEM available.
found SMP MP-table at 000f4ad0
Using x86 segment limits to approximate NX protection
On node 0 totalpages: 1048576
DMA zone: 4096 pages, LIFO batch:0
Normal zone: 225280 pages, LIFO batch:31
HighMem zone: 819200 pages, LIFO batch:31
DMI 2.3 present.
这个警告信息比较奇怪,应该是说明了问题所在。结合以前遇到过的关于 4GB 内存不能启动的问题,估计应该是内核对大内存支持的问题。于是重新下载内核源代码并进行编译,学者处理器的内存支持为 64GB,重启后就 OK 了。

在内核 config 文件中的定义是:
CONFIG_HIGHMEM4G=y
# CONFIG_HIGHMEM64G is not set
变为
# CONFIG_HIGHMEM4G is not set
CONFIG_HIGHMEM64G=y
这里对 HIGHMEM 我还不是太清楚其含义,最近正在看《LINUX 内核解析》,应该能够得到说明。

星期三, 七月 04, 2007

prev: initrd 的作用

Initrd的作用有三:

(1).提高系统的可移植性
把更多的内核功能条目编译成模块,不仅仅是为了减小内核体积,更重要的是面对各种不同的硬件架构,可以使用initrd中的linuxrc按需进行模块加载以驱动硬件,甚至对于CPU类型或者多处理机也可以进行手工选择内核(配合syslinux这类boot loader)

(2).LIVECD(光盘上的Linux)必备的部件
现在的可启动光盘都沿用1995年制定的El Torito标准,它的启动原理在于模拟软盘启动映像,这个映像的大小一般被限制在2800KB,也就是两张软盘的大小,它包含了syslinux(也可以用 isolinux,它对映像的大小要求更宽松),syslinux的配置文件,initrd.img以及内核。这时候内核的大小就必须要有约束了(当然,也有来自1MB实模式内存空间的约束),把更多的模块压缩进initrd.img中去,可以缩小启动映像的体积。
最重要的是,linuxrc脚本对于硬盘,光驱模块的加载至关重要,因为livecd要适应尽量多的硬件架构,所以它必须能按需加载模块,这时候 initrd就派上用场了。比如说,我们在vmware里运行knoppix,knoppix的linuxrc就自动加载BusLogic.o模块(通过轮换insmod来实现)。

(3).在linuxrc脚本中可以很方便地启用个性化bootsplash

http://www.linuxsir.org/bbs/showpost.php?p=751666&postcount=20

新内核提供的内核模块自动加载不是在内核初始化过程即内核态中使用的,而是在用户态进行的(比如说,你要挂载一个ntfs盘,那么内核自动加载ntfs模块),因为要在内核态加载模块,前提是内核必须确定好根文件系统,而确定根文件系统又必须要支持相应的文件系统如ext2,这就是自举的逻辑哲学问题了,所以一定要把相应的文件系统编译进内核才行!

我个人认为,研究内核一定要有方向才能成功。当今的Linux内核很庞大,源码系统可谓盘根错乱,不容易读懂,当针对某一方面来研究,比如说中断系统,显然能事半功倍。

prev: iptables DNAT/SNAT 的概念问题

先看下面的脚本:
#!/bin/bash
# [/usr/local/sbin/]ipt08_nat_DS.sh

/sbin/modprobe ipt_MASQUERADE
/sbin/modprobe iptable_nat
/sbin/modprobe ip_conntrack
/sbin/modprobe ip_conntrack_ftp
/sbin/modprobe ip_nat_ftp

iptables -F
iptables -t nat -F

# iptables -t nat -A POSTROUTING -o ppp0 -j MASQUERADE # [5]
echo 1 > /proc/sys/net/ipv4/ip_forward

inet=220.168.98.221
ihttpd=192.168.0.2

iptables -t nat -A PREROUTING -d $inet -p tcp --dport 80 -j DNAT --to-destination $ihttpd # [1]
iptables -t nat -A POSTROUTING -s $ihttpd -p tcp --sport 80 -j SNAT --to-source $inet # [2]
iptables -t nat -A POSTROUTING -d $ihttpd -p tcp --dport 80 -j SNAT --to-source $inet # [3]
iptables -t nat -A OUTPUT -d $inet -p tcp --dport 80 -j DNAT --to-destination $ihttpd # [4]


iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -m state --state NEW -i lo -j ACCEPT
iptables -A INPUT -m state --state NEW -i ! ppp0 -j ACCEPT

iptables -A INPUT -p tcp --dport 3313 -j ACCEPT # FOR: skype
iptables -A INPUT -j REJECT

iptables -A OUTPUT -j ACCEPT

iptables -A FORWARD -j ACCEPT
[1] 定义了外部的主机访问内部 http 服务器的目标地址转换规则,而 [2] 定义了该服务器的源地址转换规则。我之前一直认为有 [1] 就必须有 [2],两者必须匹配。但实际情况是,只需要有 [1],就可以让外部主机正常访问内部飞的服务器了!这是因为,当服务器响应了 iptables 所在的网关主机后,报文到达防火墙,iptables 会根据 DNAT 规则自动做 r-DNAT 操作(照着DNAT的步骤反过来做一遍),所以不再需要 SNAT 了。事实上,只要考虑一下 MASQUERADE 的工作,就可以明白──你只需要定义一个相当于 SNAT 的 转换,而不需要反过来做一次。

[2] 的作用,就是对 server 回复的报文改装源地址,从而可以对 Internet 隐藏内部网络的真实组成情况。注意,在防火墙上用 tcpdump 嗅探不会发现源地址进行了改变,因为从 server 到防火墙的报文只会经过 PREROUTING 和 INPUT 链,而 SNAT 必须到 POSTROUTING 链才生效。

设置了 [1] 后,在内部网络和防火墙主机上使用 $inet 访问服务器,会被拒绝!为什么呢?

先来看本地网络的情况:本地 $local 发送的到 $ihttpd(http server)的请求报文,因为使用 $inet,所以会经过防火墙,于是做 DNAT 操作,但没有做 SNAT 把源地址转换成防火墙的地址。http server 接到这个请求,发现源地址就在本地网络,于是把回复包直接发送到请求包的源地址 $local。

客户机 $local 接到回复报文,但它会感到“困惑”,因为它并没有向 $ihttpd 发送报文,它只好把这个包 DROP 掉,再去等待从防火墙主机 $inet 返回的“真正”的回复包,只是这个回复永远不会到达。

[3] 就是为了解决这个问题。当然,如果内部网络的主机直接使用 $ihttpd 访问 http server,就不会有上面的问题。

对于防火墙主机本身,直接用 $inet 访问 http server,也会被拒绝,因为本地产生的报文不会经过 nat 的 PREROUTING 链,而直接从 nat 的 OUTPUT 链出去,所以 [1] 定义的 DNAT 没有作用,所以,对本地报文,OUTPUT 链相当域 PREROUTING 链。

星期三, 六月 13, 2007

logcheck hack for logfiles updated by rsync

由于硬件资源的限制,对于日志分析中日志的集中过程无法利用 syslog-ng 这样的远程记录方式来完成,否则网络带宽就会成为瓶颈,而日志丢包的问题又不知道如何解决。所以使用更稳妥的办法就是利用 rsync 来同步日志到一个中央的存储主机上,然后日志分析的工作比如 logcheck for log filter 就在这里集中进行。

然而遇到一个问题,就是 logcheck 调用 logtail 时,会记录原日志文件的 inode 和 size 到一个 offset_file,其格式是:
$inode
$last_size
下次 logcheck 运行时,首先通过文件大小(wc -c)和 offset_file 中的 $last_size 进行比较,如果 $current_size "<" $last_size,那么 logcheck 就判定已经做个轮转,于是转到 logfile.1,否则仍然检查 logfile,然后会调用 logtail,logtail 会应用那个 offset_file 到选定的日志文件,通过 $last_size 记录从上次结束的地方开始读取...

但是如果使用 rsync 同步日志时,这个 inode 不会被保留,每一次 rsync 之后,日志的 inode 都会被更改,导致 logcheck 无法正确找到上一次结束的位置。如果使用 cp -f,则 indoe 会被保留,但 cp 的开销太大,特别是对于日志这样的大文件。于是我对 logcheck 又做了一个小 hack,增加了一个 --rsynced 参数:
-y    = adjust the inode number if the logfiles are updated by rsync
最终的 patch 文件是这样的:
sh$ expand -t4 logcheck-1.2.45-rsynced.patch
diff -Naur logcheck-1.2.45.old/src/logcheck logcheck-1.2.45.new/src/logcheck
--- logcheck-1.2.45.old/src/logcheck 2006-07-06 18:16:42.000000000 +0800
+++ logcheck-1.2.45.new/src/logcheck 2007-06-15 10:50:34.000000000 +0800
@@ -49,7 +49,7 @@
ATTACK=0

# Set the getopts string
-GETOPTS="c:dhH:l:L:m:opr:RsS:tTuvw"
+GETOPTS="c:dhH:l:L:m:opr:RsS:tTuvwy"

# Get the details for the email message
DATE="$(date +'%Y-%m-%d %H:%M')"
@@ -90,6 +90,10 @@
LOCKDIR=/var/lock/logcheck
LOCKFILE="$LOCKDIR/logcheck"

+RSYNCED=0
+# If the logfiles are centralized by rsync, the inode number will not be
+# reserved, so add an option for this condition
+
# Carry out the clean up tasks
cleanup() {

@@ -208,7 +212,9 @@
mkdir $cleaned \
|| error "Could not make dir $cleaned for cleaned rulefiles."
fi
- for rulefile in $(run-parts --list $dir); do
+ debug "find $dir, pwd: `pwd`"
+ for rulefile in $(find $dir -type f -perm +0100); do
+ debug "rulefile $rulefile"
rulefile=$(basename $rulefile)
if [ -f ${dir}/${rulefile} ]; then
debug "cleanrules: ${dir}/${rulefile}"
@@ -406,6 +412,18 @@
fi
}

+rsynced() {
+ logfile=$1
+ offsetfile=$2
+ if [ $RSYNCED -eq 1 ]; then
+ debug "$logfile rsync specified"
+ new_inode=$(ls -i $logfile | awk '{print $1}')
+ old_inode=$(head -n1 $offsetfile)
+ debug "replace $old_inode with $new_inode"
+ sed -i "1s/^.*$/$new_inode/" $offsetfile
+ fi
+}
+
# Get the yet unseen part of one logfile.
logoutput() {
file=$1
@@ -415,11 +433,13 @@
if [ -f "$file" ]; then
offsetfile="$STATEDIR/offset$(echo $file | tr / .)"
if [ -s "$offsetfile" -a -r "$offsetfile" ]; then
+ rsynced $file $offsetfile
if [[ $(wc -c < "$file") -lt $(tail -n 1 "$offsetfile") ]]; then
# assume the log is rotated by savelog(8)
# syslog-ng leaves old files here
if [ -e "$file.0" -a "$file.0" -nt "$file.1.gz" ]; then
debug "Running logtail on rotated: $file.0"
+ rsynced $file.0 $offsetfile
$LOGTAIL -f "$file.0" -o "$offsetfile" $LOGTAIL_OPTS > \
$TMPDIR/logoutput/$(basename "$file") 2>&1 \
|| error "Could not run logtail or save output"
@@ -429,6 +449,7 @@
# should also probably check if file is still fresh
elif [ -e "$file.1" ]; then
debug "Running logtail on rotated: $file.1"
+ rsynced $file.1 $offsetfile
$LOGTAIL -f "$file.1" -o "$offsetfile" $LOGTAIL_OPTS > \
$TMPDIR/logoutput/$(basename "$file") 2>&1 \
|| error "Could not run logtail or save output"
@@ -452,7 +473,7 @@
debug "usage: Printing usage and exiting"
cat"<<"EOF
usage: logcheck [-c CFG] [-d] [-h] [-H HOST] [-l LOG] [-L CFG] [-m MAIL] [-o]
- [-r DIR] [-s|-p|-w] [-R] [-S DIR] [-t] [-T] [-u]
+ [-r DIR] [-s|-p|-w] [-R] [-S DIR] [-t] [-T] [-u] [-y]
-c CFG = override default configuration file
-d = debug mode
-h = print this usage information and exit
@@ -471,6 +492,7 @@
-u = enable syslog-summary
-v = print version
-w = use the "workstation" runlevel
+ -y = adjust the offset inode number if the logfiles are updated by rsync remotely
EOF
}

@@ -600,6 +622,10 @@
debug "Setting REPORTLEVEL to workstation"
REPORTLEVEL="workstation"
;;
+ y)
+ debug "Setting RSYNCED"
+ RSYNCED=1
+ ;;
\?)
usage
exit 1
这里没有使用长格式的参数 --rsynced,而是短格式 -y(-r/-s 都已经被使用了),因为 logcheck 使用的是 bash 内置 getopts 而不是 GNU 的 getopt,而 getopts 不支持长格式的参数,如果改为使用 getopt,则要更改的内容太多,所以最后还是选择一个比较简单的方法吧。

注意这里轮转后的日志由于也是 rsync 同步的,所以也要应用 rsynced() 函数对 inode 做调整。

另外,从 root 调用 logcheck 的时候,可以使用 sudo 或 su,但记得要更改 $HOME 目录,对 su,可以使用 su - logcheck,对 sudo,可以使用 sudo -u logcheck -H。如果不更改 $HOME,会导致 find 命令出错(find $dir -type f -perm +0100,就是前面为避免使用 run-parts --list 而做的一个小 hack):
find: cannot get current directory: Permission denied
所以调用脚本可以写成:
#!/bin/sh

PATH=$PATH:/usr/sbin
datadir=/data/hosts
echo "DEBUG: $datadir"

find $datadir -type f | xargs setfacl -m user:logcheck:4
find $datadir -type d | xargs setfacl -m user:logcheck:5
# sudo -u logcheck -H /usr/sbin/logcheck $@
su - logcheck -c "/usr/sbin/logcheck $@"

星期一, 六月 11, 2007

cifs uid/gid overwrite

默认情况下,使用 cifs 挂载 samba 后,挂载文件系统的文件属主是有问题的:
smbclient# ls /mnt/host/ -l
total 24
-rw-r--r-- 1 root root 372 May 25 09:55 adjust
drwxr-xr-x 5 10003 10003 0 Jun 11 11:37 fs_backup
drwxr-xr-x 12 10003 10014 0 Jun 11 11:37 logs
这意味这文件属于了不该属于的用户。但即使使用:
smbclient# mount //store/homes -t cifs /mnt/host -o uid=0,gid=0,username=host_p01,password='********'
挂载也没有用,文件属主还是不对。

从 man 手册的情况来看,是有一个 Unix Extensions 在产生影响。要关闭这个选项,需要在 Samba 客户机上执行:
smbclient# echo "0">/proc/fs/cifs/LinuxExtensionsEnabled
然后再重新挂载 cifs 文件系统,不需要使用 uid,gid 参数,也会映射到 root 用户:
smbclient# mount //store/homes -t cifs /mnt/host -o username=host_p01,password='********'
smbclient# ls /mnt/host/ -l
total 24
-rwxrwSrwt 1 root root 372 May 25 09:55 adjust
drwxrwxrwx 1 root root 0 Jun 11 11:37 fs_backup
drwxrwxrwx 1 root root 0 Jun 11 11:37 logs
但是这样一来,文件的权限又有问题了!!! 只能通过增加参数来解决这个问题:
smbclient# mount //store/homes -t cifs /mnt/host -o file_mode=0644,dir_mode=0755,username=host_p01,password='********'
但是符号链接仍然不能使用。鱼与熊掌呀,现在只能如此了,好在这边应用中还没有必须用到 symlink 的地方,希望后续版本能够解决这个问题

smbclient# cd /mnt/hosts/
smbclient# ln logs/ -s test
ln: creating symbolic link `test' to `logs/': Operation not supported
如果要在服务器端关闭 Unix Extensions,在 /etc/samba/smb.conf 中编辑:
unix extensions = no

星期五, 六月 08, 2007

A small hack for logcheck-1.2.45

对于 log filter,logcheck 是比较好的选择,但是从 1.1.1 到 1.2.45,还是有比较大的变化,例如 logtail 由 C 程序改成了 perl 脚本。但最主要的一点是 logcheck-1.2.45 的设计上正交性更好,而且提供了针对各种服务和应用的更多模式,因而可以给管理员更多的自由选择。因此可以省去很多使用 1.1.1 的情况下必须自己编写模式的麻烦,而且 1.2.45 的模式匹配也更为精确。

问题主要在于 logcheck-1.2.45 的安装比较麻烦,而且由于使用的是 shell 脚本,所以平台相关性比较严重一点,而且对于依赖性的检查不好。logcheck-1.2.45 依赖于 lockfile-progs,但除非你安装了 logcheck 并运行,你不会知道这一点,而 lockfile-progs 在安装时也会出现编译错误,因为缺少 lockfile.h 这个头文件,但它并不会告诉你这是因为还需要安装 liblockfile-1.06.2 这个包。

安装了所有这些之后,将 /var/log 拷贝成 /tmp/log,然后:
sh# chown logcheck.logcheck /tmp/log -R
sh# vi /etc/logcheck/logfiles
/tmp/log/messages
/tmp/log/maillog
/tmp/log/secure
sh# su - logcheck
sh$ /usr/sbin/logcheck

不会有输出,从接收到的邮件中分析,发现没有做任何过滤,但是 /etc/logcheck/ignore.d.server/* 中却确实有相应的模式!

因此我分析了一下 /usr/sbin/logcheck 这个 shell 程序,找到寻找模式文件的那部分
......
cleanrules "$RULEDIR/cracking.d" $TMPDIR/cracking
cleanrules "$RULEDIR/violations.d" $TMPDIR/violations
cleanrules "$RULEDIR/violations.ignore.d" $TMPDIR/violations-ignore

# Now clean the ignore rulefiles for the report levels
for level in $REPORTLEVELS; do
cleanrules "$RULEDIR/ignore.d.$level" $TMPDIR/ignore
done

# The following cracking.ignore directory will only be used if
# $SUPPORT_CRACKING_IGNORE is set to 1 in the configuration file.
# This is *only* for local admin use.
if [ $SUPPORT_CRACKING_IGNORE -eq 1 ]; then
cleanrules "$RULEDIR/cracking.ignore.d" $TMPDIR/cracking-ignore
fi
......
cleanrules() {
dir=$1
cleaned=$2

if [ -d $dir ]; then
if [ ! -d $cleaned ]; then
mkdir $cleaned \
|| error "Could not make dir $cleaned for cleaned rulefiles."
fi
for rulefile in $(run-parts --list $dir); do
rulefile=$(basename $rulefile)
if [ -f ${dir}/${rulefile} ]; then
debug "cleanrules: ${dir}/${rulefile}"
if [ -r ${dir}/${rulefile} ]; then
# pipe to cat on greps to get usable exit status
egrep --text -v '^[[:space:]]*$|^#' $dir/$rulefile | cat \
>> $cleaned/$rulefile \
|| error "Couldn't append to $cleaned/$rulefile. Disk Full?"
else
error "Couldn't read $dir/$rulefile"
fi
fi
done
elif [ -f $dir ]; then
error "cleanrules: '$dir' is a file, not a directory"
elif [ -z $dir ]; then
error "cleanrules: called without argument"
fi
}
可以看到,寻找模式文件的操作由 cleanrules() 函数来完成,而实际上有哪些文件需要应用是由 run-parts --list $dir 这个命令来查找的。增加一个 DEBUG 输出来查看有那些 rulefiles 被应用了,结果发现出错信息。

单独运行:
sh# run-parts --list /etc/logcheck/ignore.d.server/
Not a directory: --list
sh# run-parts /etc/logcheck/ignore.d.server/ --list
# EMPTY!
所以这样实际上没有找到任何文件。

从 google search 的情况来看,run-parts 的平台相关性比较大,这个命令是用来寻找一个目录下那些有执行权限的文件的,如果不使用 --list,就会执行这些文件。当然前提是这个 run-parts 有这个参数,而 RHEL4 上面的这个 run-parts 就没有这个参数(实际上只有一个 PATH 参数),而 logcheck 的开发者似乎对 debian 比较熟悉,所以这里不能直接使用。

可以做一个修改,将 run-parts 命令改为:
find $dir -type f -perm +0100
即可。对于使用 ulfs 安装,相应的 profile 为:
sh# cat /usr/src/logcheck/.config
pkgname = "logcheck";
version = "1.2.45";
user = "logcheck";
groups = "";
group = "logcheck";
archive = "logcheck_1.2.45.tar.gz";
command = "tar xfz logcheck_1.2.45.tar.gz";
command = "cd logcheck-1.2.45";
command = "sed -i 's/install -d/mkdir -p/g' Makefile";
command = "sed -i 's/run-parts --list $dir/find $dir -type f -perm +0100/g' src/logcheck";
command = "make";
command = "cd ..";
command = "rm -rf logcheck-1.2.45";
time = "20070608 10:49:35 Fri"
然后,在 /etc/logcheck/ignore.d.server 下,对那些需要用到的模式文件,使用 chmod u+x 增加可执行权限,这样这些文件就会在过滤的时候被用到!因此可以看到这种方法提供了更高的正交性和灵活性。

星期四, 六月 07, 2007

hostname in syslog

如果使用 syslog 或 syslog-ng 集中日志,或者使用其他方法集中日志(),然后在中心主机上用统一的过滤程序过滤日志并生成每日的报告,一个很重要的问题就是在日志记录中 hostname 字段必须正确唯一,否则两台主机使用同一个 hostname,那就混乱了。

如果 syslog 的 hostname 字段与系统的 hostname command 输出或 /etc/sysconfig/network 中 HOSTNAME= 不一致,只需要重启 syslogd 即可:/etc/init.d/syslog restart

星期三, 六月 06, 2007

From a Samba I/O problem

昨天调整网络的时候发现一个问题,因为之前都是用 Samba 做网络文件系统共享,使用 mount 挂载到本地来做一些备份操作等,昨天在没有 umount 的情况下更改 IP 地址网段,结果发现再运行 df/fuser/lsof 等命令都会导致其进程挂起,使用 ps 显示为状态"D"(Down or Deadlock?),即 Uninterruptable Sleep (Unusally I/O)。这些进程无法用 kill,即使使用 kill -9 也不行。

在网上查了一下,仔细想了一想,觉得这样的机制还是有道理的,因为这种情况通常反映的是 I/O 错误,最常见的就是磁盘错误,如果磁盘损害,出现了无法修复的错误,应该曝出这个错误,而不能使进程可以被 kill 掉。

事实上,对于 NFS 也是一样的,并且这和先终止服务器端的 smbd 进程不同:如果先终止 smbd,不会出现这样的 I/O 问题。

这些进程只能通过重启机器来消除,或者先把 IP 地址改回来,待 umount 之后再重新更改 IP 地址。对于网络文件系统,可以考虑更改 network SysV init 脚本,将相应的检查和操作加入其中。

星期二, 五月 29, 2007

rpm -qa --last

按照安装时间进行排序,这样可以看到最后安装的包,从而对配置的备份等作出调整。

星期五, 五月 18, 2007

apache logrotate

必须设置 Apache 的日志自动轮转,否则其日志增长可能会很快,最终撑爆硬盘。在 /etc/logroate.conf 或 /etc/logrotate.d/httpd 中加入:
/usr/local/apache2/logs/*log {
monthly
rotate 3
}
但这样还不够,因为日志轮转后,如果不重启 httpd 进程,那么日志就会写到 access_log.1 中。可以在 logroate 配置中通过如下方法来指定重启:
    postrotate
/bin/kill -HUP `cat /usr/local/apache2/logs/httpd.pid 2>/dev/null` 2> /dev/null || true
endscript
}

星期四, 五月 17, 2007

SNMP shell script for process monitor

一个临时解决办法,为了监测进程的健康状态,首先必须在被控机上安装 net-snmp,然后配置 /etc/snmp/snmpd.conf 如下:
syscontact  sysadm@sample.com
proc vsftpd 100 1
proc httpd 3000 1
proc mysqld 3000 1
disk /data 100G
com2sec mynet 192.168.0.0/24 process-mon
group mynet v1 mynet
group mynet v2c mynet
view system included .1.3.6.1.2.1
view system included .1.3.6.1.4.1.2021.2
access mynet "" any noauth exact system none none
接着编辑脚本:

#!/bin/sh

PATH=$PATH:/usr/bin
PROGRAM=`basename $0`

community='demo'
host='localhost'
mailto=''
items=''
ilist='tmp'

args=`getopt -l help,item:items-list: c:h:m:i:I: $*`
if [ $? -gt 0 ]; then
strerr="Invalid options"
echo "$strerr" >&2
logger -it "$strerr"
exit 1
fi

for i in $args; do
case $i in
-c) shift; community=$1; shift;;
-h) shift; host=$1; shift;;
-m) shift; mailto=$1; shift;;
-i|--item)
shift
if [ -n "$items" ]; then
items=`echo -e "$items\n$1"`
else
items="$1"
fi
shift
;;
-I|--items-list)
shift; ilist=$1; shift;;
--help)
shift
echo "useage: $PROGRAM [-c|-h|-m] [--item|--help]
-c community
-h host
-m mailto
-i|--item item_map, 'community host' map
--help, print this message"
exit 0
;;
esac
done

if [ -z "$items" ]; then
items="$community $host"
fi

STRERR=""
if [ "$ilist" == "tmp" ]; then echo "$items" >$ilist; fi
while read community host; do
# echo "$items" | while read community host; do
# for item in "$items"; do
# community=`echo $item | awk '{print $1}'`
# host=`echo $item | awk '{print $2}'`
if [ -z "$community" -o -z "$host" ]; then
strerr="Invalid 'community host' item"
echo strerr >&2
logger -it $PROGRAM "$strerr"
continue
fi

snmp_result=`snmpwalk -v2c -c $community $host UCD-SNMP-MIB::prNames`
if [ $? -gt 0 ];then
strerr="$host: SNMP ERROR, maybe system is down"
echo "$strerr" >&2
logger -it "$PROGRAM" $strerr
STRERR="$STRERR\n$strerr"
fi
num=`echo "$snmp_result" | wc -l` && \
for i in `seq 1 $num`; do
status=$(snmpget -v2c -c $community $host UCD-SNMP-MIB::prErrorFlag.$i | awk -F' = ' '{print $2}') && \
process=$(echo "$snmp_result" | sed -n "$i{s/^UCD-SNMP-MIB::prNames.$i = STRING: \(.*\)$/\1/p}") && \
if [ "$status" == "INTEGER: error(1)" -o "$status" == "INTEGER: 1" ]; then
message=$(snmpget -v2c -c $community $host UCD-SNMP-MIB::prErrMessage.$i | sed "s/UCD-SNMP-MIB::prErrMessage.$i = STRING: \(.*\)$/\1/")
strerr="$host: $message"
echo "$strerr" >&2
logger -it $PROGRAM "$strerr"
STRERR="$STRERR\n$strerr"
fi
done
done <$ilist
# rm -f tmp

if [ -n "$mailto" ] && [ -n "$STRERR" ]; then
STRERR="*** CRITICAL ERRORs ***\n$STRERR"
echo -e "$STRERR" | mail -s "*** CRITICAL: process monitor ERRORs reporting ***" $mailto
fi
这个脚本可以这样运行:
sh$ process-mon -c demo -h 192.168.0.98
sh$ process-mon -i 'demo 192.168.0.98' -i 'mon 192.168.0.99' -m chowroc@sample.com
检查两台主机并在有问题时发送邮件到(mailto)指定的的地址
sh$ process-mon -I process-mon.list -m roc@sample.com
sh$ cat process-mon
demo 192.168.0.98
mon 192.168.0.99
...