电脑技术学习

应用部署为Solaris 10 SMF服务

dn001

  SMF功能概览

  Solaris 10中的SMF提供了强大的服务管理功能。以下是一部分重要的功能:

  1. SMF向系统管理员提供统一的服务管理平台,利用svcs(1)命令就可以查看SMF所辖的各种服务,利用svccfg(1M)和svCADm(1M)命令可以配置各种服务和管理各种服务。对于传统Unix,系统管理员必须记住各种服务不同的启动/停止方法、配置修改方法、服务状态及日志查询方法而言, SMF统一管理平台极大地降低了系统管理的难度,也降低了系统管理出错的机率。

  2. SMF提供各服务间的依赖关系设定,可以自动按依赖关系顺序启动各服务。这对于传统UNIX以rc脚本文件名排列先后决定启动/停止顺序而言,SMF提供了无可比拟的完善的管理能力。

  3. 并行启动不相互依赖的服务,从而使系统启动更快。由于各服务的依赖关系在SMF中有明确的定义,所以不相干的服务完全可以并行启动而不必担心冲突。

  4. 自动侦测所辖服务的运行状态,在必要时可以重启服务或停止服务。作为预测性自愈技术(Predictive Self-Healing)的组成部分,SMF可以对所辖服务进行状态监控。根据服务的需要,SMF可以在服务进程不存在时,自动重启服务,或者在服务所依赖关系发生问题时,重启服务。也可以在服务连续发生问题时,将服务置为维护(maintenance)状态。

  当然,SMF的管理机制并不排斥传统rc脚本运行服务的机制,以最大程度兼容传统方式的运作。有关SMF更多的介绍,请参看Solaris Service Management Facility - Quickstart Guide。

  一个简单的服务程序

  <表1是一个简单的程序myapp.c,它运行后将成为后台守护进程存在于系统中,并每间隔5秒钟向日志文件/tmp/myapp.log插入一行记录以报告自己的存在。虽然它实际上不向外提供任何服务,但该程序模拟了一般服务程序的设计结构和运行模式。即,程序运行后以守护进程形式存在于系统,程序头部有模拟服务配置read_config()和初始化app_init()逻辑,中部使用sleep(5)模拟等待服务请求逻辑,通过向日志插入记录模拟服务请求处理逻辑,然后返回至循环开始处sleep(5)继续等待下一个服务请求等。只要在此结构上修改和扩充相应的逻辑就可以将此程序修改为一个真正的服务程序。本文要点是说明如何部署应用为SMF服务,所以仅采用此程序作为例子。

表1.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/***************************************/
/* myapp.c */
/***************************************/
#include <unistd.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
/* global exit status code */
#define RUN_OK 0
#define CONFIG_ERROR 1
#define FATAL_ERROR 2
/* function declaration */
int read_config(void);
int app_init(void);
void daemonize(void);
int main(int argc, char **argv)
{
FILE *fp;
time_t t;
/* Read the app configuration here if applicable. */
/* If error occurred, return non-zero. */
if (read_config() != RUN_OK)
{
printf("%s: read configuration failuren", argv[0]);
exit(CONFIG_ERROR);
}
/* Initialize application. Any error, return non-zero. */
if (app_init() != RUN_OK)
{
printf("%s: initialization failuren", argv[0]);
exit(FATAL_ERROR);
}
daemonize(); /* Make the application a daemon. */
/* Service logic is placed here. */
while (1)
{
sleep(5); /* Sleep for 5 seconds. In a real application, it could be */
/* waiting for service requests, e.g. waiting on message */
/* queue, etc. */
/* service logic */
if ((fp = fopen("/tmp/myapp.log","a")) != NULL)
{
t = time(0);
fprintf(fp, "myapp is running at %sn",asctime(localtime(&t)));
fclose(fp);
}
else
{
exit(FATAL_ERROR);
}
}
}
/* make the application a daemon */
void daemonize(void)
{
int pid;
int i;
if (pid = fork())
exit(RUN_OK); /* parent exits */
else if (pid < 0)
exit(FATAL_ERROR); /* fork() failed */
/* first child process */
setsid();
if (pid = fork())
exit(RUN_OK); /* first child exits */
else if(pid < 0)
exit(FATAL_ERROR); /* fork() failed */
/* second child */
for (i = 0; i < NOFILE; ++i) /* close all files */
close(i);
chdir("/"); /* change Directory to root(/) directory */
umask(0); /* set proper file mask */
}
/* read configuration */
int read_config(void)
{
return RUN_OK; /* OK */
}
/* initialize application */
int app_init(void)
{
return RUN_OK; /* OK */
}

  利用Sun公司最新推出的C/C++/Fortran开发及编译环境Sun Studio 11,使用以下命令将myapp.c编译成可执行程序myapp。

$ /opt/SUNWspro/bin/cc -o ./myapp ./myapp.c

  或者直接使用Solaris 10自带的gcc编译器将myapp.c编译成可执行程序myapp。

$ /usr/sfw/bin/gcc -o ./myapp ./myapp.c

  编译成功后在当前目录下会生成myapp可执行程序。本例假设当前目录为/export/home/smfdemo。直接在命令行输入. /myapp就可以启动myapp为后台守护进程,同时可以在/tmp目录下找到myapp.log文件。使用“/usr/bin/tail -f /tmp/myapp.log命令可以看到进程myapp不断在日志文件中报告自己的存在。为了使这个进程在服务器启动时自动运行,传统做法一般需要写三个shell脚本。其中一个shell脚本置于/etc/init.d目录下用以实际启动和停止myapp服务(例如<表2所示的/etc/init.d/myapp.sh)。另外两个shell脚本的名字分别以S和K开头(例如,S79myapp和K79myapp),置于合适的/etc/rc?.d目录下(例如,/etc/rc2.d目录下),分别以不同参数(start和stop)调用 /etc/init.d/myapp.sh。操作系统在boot时会自动运行S开头的脚本以启动myapp服务,而在shutdown时自动运行K开头的脚本以关闭myapp服务。

表2.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/sbin/sh
###############################################################################
# /etc/init.d/myapp.sh #
###############################################################################
RUN_OK=0
CONFIG_ERROR=1
FATAL_ERROR=2
case "$1" in
'start')
/export/home/smfdemo/myapp
if [ $? -eq $CONFIG_ERROR ]; then
exit $CONFIG_ERROR
fi
if [ $? -eq $FATAL_ERROR ]; then
exit $FATAL_ERROR
fi

'stop')
/usr/bin/pkill myapp

*)
echo "Usage: $0 { start | stop }"

esac
exit $RUN_OK

  SMF可部署的服务

  本节讲述如何将上述例子改为SMF可部署的服务。根据SMF的要求,开发一个SMF可部署的服务需要至少以下几个步骤。

  创建manifest文件

  SMF manifest文件是一个XML文件,它用以定义SMF服务各属性。比如,定义服务名称、服务依赖关系、服务启动方法、服务停止方法、服务所需参数等。创建manifest文件最简单的方法是从/var/svc/manifest目录下挑选已存在的相同类型的服务XML文件,将它拷贝到开发目录,比如/export/home/smfdemo目录下,以拷贝件为基础修改而成。本文是个简单的服务,所以参考了 /var/svc/manifest/system/utmp.xml文件(因为它也很简单),在其基础上修改成表3所示的/export/home/smfdemo/myapp.xml。

表3.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?xml version="1.0"?>
<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
<service_bundle type='manifest' name='mypackage:myapp'>
<service
name='application/myapp'
type='service'
version='1'>
<create_default_instance enabled='true' />
<single_instance/>
<dependency
name='milestone'
grouping='require_all'
restart_on='none'
type='service'>
<service_fmri value='svc:/milestone/multi-user' />
</dependency>
<exec_method
type='method'
name='start'
exec='/export/home/smfdemo/myapp.sh start'
timeout_seconds='60' />
<exec_method
type='method'
name='stop'
exec=':kill'
timeout_seconds='60' />
</service>
</service_bundle>

  myapp.xml必须符合/usr/share/lib/xml/dtd/service_bundle.dtd.1规范,其意理解如下:

  1. <service_bundle>标签用以告知SMF如何处理myapp.xml文件。本例中myapp.xml是一个manifest文件用以定义SMF服务,所以type赋为“manifest。同时需要给service_bundle一个名字,一般命名规范是以服务所在安装包名为前缀,所以本例将name赋为“mypackage:myapp。其实name只要不与系统中已有的相重就可以了,当然对于企业级应用服务应该有一个合适的名字。

  2. <service>标签主要定义SMF服务的名称,由于myapp只是一个简单应用,所以name赋为“application/myapp。如果myapp是网络服务,则根据命名规范名字应以“network/开头加myapp,即 “network/myapp,请参考/var/svc/manifest/下的目录结构以此类推。type当然应赋为“service。至于version,根据情况设定,缺省取1。

  3. 根据需要SMF服务可以为同一个服务启动多个实例(instance)。比如,在系统中同一种数据库平台可以启动多个服务实例,分别服务于不同的应用;或者同一种WEB服务平台启动多个服务实例,在不同的端口提供不同WEB应用服务等。在SMF框架中只需定义一个SMF service及属性,在同一个service下定义不同的instance和特定属性即可。service下已定义的属性适用于所有instance,但任何一个instance也可以根据需要特定某个或某几个属性。比如增加属性或覆盖service同名属性定义。由于本例非常简单,只需一个服务一个实例就行了,所以采用标签<single_instance/>,所有属性全部采用service中的属性即可。

  4. 由于希望myapp服务在系统boot时自动启动,所以将<create_default_instance>标签中enable置为“true。

  5. <dependency>是manifest文件中最难定义的部分,它定义了此服务所依赖的其他资源,包括服务、文件系统等。一个SMF服务根据需要可以定义多个<dependency>,每个<dependency>具有自己的标识名name、grouping、 restart_on、type,以及所依赖的各资源的service_fmri。其中name只是个标识,不相重有意义即可。grouping取值定义了所列其他服务与本资源的依存关系,取值“require_all是指当所列其他资源全部启动和可用后才能满足本服务启动的要求。restart_on 规定了当所依赖的其他资源发生何种情况时需要重启本服务,取值“none是指只要本服务处于运行状态就行了,不必考虑所依赖的其他资源的状态是否改变。 type指向依赖资源的类型,比如“service指服务,“path指文件系统等。service_fmri指其他服务的FMRI(Fault Management Resource IdengifIEr)。本例仅需在/tmp目录下生成日志文件,而“milestone/multi-user所指的运行状态完全可以满足要求,所以 service_fmri设为“svc:/milestone/multi-user。

  有关milestone的概念请参看Solaris Service Management Facility - Quickstart Guide。

  6. manifest文件必须定义启动和停止服务的方法,<exec_method>标签即用于此目的。原先利用/etc/init.d/myapp.sh加合适的参数即可启动和停止myapp,现仍可利用。不过myapp.sh需作小小改动,改动方法请参看下节启停方法客户化。对于启动和停止,分别需要定义两个method,它们的type当然都为“method,其中一个name设为为“start,exec表示执行什么动作以完成这个start方法,其动作设为“/export/home/smfdemo/myapp.sh start。另一个name设为“stop,由于原脚本是使用pkill命令杀掉myapp进程,所以这里可以直接将“:kill赋给exec,表示SMF可直接杀掉myapp服务相关的所有进程。timeout_seconds定义了完成启动和停止服务操作所需的最长时间,如果在这个时间内未能完成相应操作,SMF会认为服务存在问题,因为会将服务置为maintenance状态,由人工进行排错。本例中,timeout_seconds设为60秒足矣。

  事实上还有许多标签项目可以设定,但对于本例不是必要的,所以可省略不设。有关manifest文件编写更详细信息,请参看Solaris Service Management Facility - Service Developer Introduction。

启停方法客户化

  SMF框架中svcs(1)命令非常有用,它不但可以列出系统中所有的服务资源及状态,还可以提供那些未正常启动的服务的出错原因、影响范围和可能的恢复方法等。比如,它可以报告说某个服务因为配置不正确而未正常启动,或者某服务遇到致使错误请参SMF的某个日志文件等。SMF之所以能够提供这些信息是由于启动和停止方法提供了相关的信息。SMF要求所有启动和停止方法必须返回一组特定的值,具体值可以参看Solaris 10操作系统/lib/svc/share/smf_include.sh文件尾部。

  本例中,表2所示的/etc/init.d/myapp.sh可能返回3种值,$CONFIG_ERROR、$FATAL_ERROR和$RUN_OK。现目标是要替换原返回值为相应的SMF返回值,如果没有相应的SMF返回值,则替换为最合适的SMF返回值,使服务非正常退出时,SMF能够报告可令人接受的错误原因。本例修改方法如下:

1. 首先,将表2所示的/etc/init.d/myapp.sh文件拷贝到开发目录下,比如/export/home/smfdemo目录下。后面步骤中所有修改都改在拷贝内。

  2. 通过增加“. /lib/svc/share/smf_include.sh到myapp.sh头部,将SMF所需的各返回变量和过程包含到myapp.sh脚本。

  3. 替换“exit $CONFIG_ERROR为“exit $SMF_EXIT_ERR_CONFIG,因为$SMF_EXIT_ERR_CONFIG与原退出码$CONFIG_ERROR退出原因最相近。

  4. 替换“exit $FATAL_ERROR为“exit $SMF_EXIT_ERR_FATAL,因为$SMF_EXIT_ERR_FATAL与原退出码$FATAL_ERROR退出原因最相近。

5. 替换“exit $RUN_OK为“exit $SMF_EXIT_OK,因为$SMF_EXIT_OK与原退出码$RUN_OK退出原因最相近。

  6. 删除stop case及其操作。因为stop方法已在myapp.xml中另行处理,不再需要myapp.sh了。

  7. 修改default case中的echo,以反映正确的usage。

  修改后的myapp.sh如表4所示。至此,所有前期准备工作都已完成,下面就可以进行部署了。

表4.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/sbin/sh
###############################################################################
# /export/home/smfdemo/myapp.sh #
###############################################################################
. /lib/svc/share/smf_include.sh
RUN_OK=0
CONFIG_ERROR=1
FATAL_ERROR=2
case "$1" in
'start')
/export/home/hunter/smf/myapp_smf
if [ $? -eq $CONFIG_ERROR ]; then
exit $SMF_EXIT_ERR_CONFIG
fi
if [ $? -eq $FATAL_ERROR ]; then
exit $SMF_EXIT_ERR_FATAL
fi

*)
echo "Usage: $0 start"

esac
exit $SMF_EXIT_OK

  部署我的应用为SMF服务

  管理和修改SMF服务分别需要solaris.smf.manage和solaris.smf.modify权限,具体请参看 smf_security(5)。缺省只有root有此权限,可使用root部署SMF服务。如果使用普通用户账号,则需要root将solaris.smf.manage和 solaris.smf.modify权限赋予相关用户。方法是在/etc/user_attr文件中加入授权记录。比如为用户hunter加入SMF管理和修改权限,则/etc/user_attr显示如下,其中粗斜体部分为hunter所需的权限。

  # cat /etc/user_attr
#
# Copyright (c) 2003 by Sun Microsystems, Inc. All rights reserved.
#
# /etc/user_attr
#
# user attributes. see user_attr(4)
#
#pragma ident "@(#)user_attr 1.1 03/07/09 SMI"
#
adm::::profiles=Log Management
lp::::profiles=Printer Management
root::::auths=solaris.*,solaris.grant;profiles=Web Console Management,All;lock_after_retries=no
hunter::::auths=solaris.smf.manage,solaris.smf.modify

  假设本例中开发目录和所有文件都位于/export/home/smfdemo目录下,则将本例部署为SMF服务的步骤如下:

  1. 使用svccfg(1M)命令检查myapp.xml文件是否符合XML规范。如果没问题则不会有任何输出,否则根据出错提示修改myapp.xml。

# /usr/sbin/svccfg validate /export/home/smfdemo/myapp.xml

  2. 使用svcs(1)命令看是否已存在名为myapp的服务。如有则必须修改在myapp.xml中定义的服务名,否则继续。

  # /usr/bin/svcs application/myapp

  3. 使用svccfg(1M)命令加载myapp.xml所定义的服务并自动启动服务。

  # /usr/sbin/svccfg import /export/home/smfdemo/myapp.xml

  4. 使用svcs(1)命令查看myapp服务状态。如状态为online,则说明部署已成功且已运行,否则参看出错原因以及SMF日志以确定问题所在,然后重复上文中相关的步骤后再试。

  # /usr/bin/svcs -xv application/myapp

  至此,我的应用myapp已经成功部署为SMF。

  其他操作

  myapp成为SMF服务后可以使用以下命令进行管理。

  1. 要禁用myapp服务,请使用/usr/sbin/svcadm disable application/myapp。

  2. 要再次启用myapp服务,请使用/usr/sbin/svcadm enable application/myapp。

  3. 要重启myapp服务,请使用/usr/sbin/svcadm restart application/myapp。

  4. 当myapp服务出现配置错误或其他原因致使myapp的状态为maintenance时,在解决错误原因后,可使用/usr/sbin/svcadm clear application/myapp清除maintenance状态。

  5. 当需要对myapp进行维护时,可将其状态改为maintenance状态,方法是/usr/sbin/svcadm mark application/myapp。

  6. 可使用svccfg(1M)命令对myapp进行配置管理。具体方法请参看svccfg(1M)使用说明。

  总结

  Solaris 10操作系统是Sun公司最新的下一代操作系统,包含了600多项革新技术,SMF技术就是其中之一。通过使用SMF技术,系统中所有的服务可以在一个统一而强大的平台中进行配置和管理。同时,它也是预测性自愈技术的组成部分,可以自我侦测各服务的运行状态,尽可能地减少服务下线的机率。另外,利用SMF 技术系统管理员可以降低系统维护难度,减少人为出错机会。让我们把自已的应用尽早地部署到SMF框架中去吧。

  参考资料

  1. Predictive Self-Healing at BigAdmin System Administration Portal
2. SMF System Administration Guide
3. Solaris 10操作系统/usr/share/lib/xml/dtd/service_bundle.dtd文件
4. Solaris 10操作系统上,以下man页面:

  netadm(1M)

  inetconv(1M)

  inetd(1M)

  kernel(1M)

  smf(5)

  smf_bootstrap(5)

  smf_method(5)

  svc.startd(1M)

  svcadm(1M)

  svccfg(1M)

  svcprop(1)

  svcs(1)

标签: