欢迎加入IT云增值在线QQ交流群:342584734
分类:
2006-04-24 17:35:43
作者:Hunter Li, Sun Microsystems, Inc.
Solaris 10的SMF机制向系统管理员提供了统一的服务管理平台,利用它可方便地对所辖服务进行配置和管理,还可以在必要的时候自动重启服务。SMF的配置功能不但可让系统管理员在服务下线的状态下通过svccfg(1M)配置服务属性,还可以在服务联机的状态下结合使用svcadm refresh命令,动态配置和启用最新属性。另外,SMF还提供编程接口API(包含在libscf.so库中),利用它可以取代用户的配置文件以及相应的配置读取解析逻辑,享受专业级配置工具svccfg(1M)带来的便利。本文将结合一个例子讲述如何在应用程序中使用libscf.so所提供的部分API实现SMF服务refresh方法。
对于大部分服务类应用而言,为了适应运行环境的多变性,一般都会在服务程序中编写配置读取模块,在服务启动时通过读取配置来决定如何提供服务。比如,一个典型的网络服务可能在服务启动时通过读取配置文件决定网络服务端口、服务日志路径等。配置的存在形式有多种,比如配置文件、数据库记录,甚至简单到使用操作系统环境变量等。无论配置采用何种形式存在,为了能获取配置值,服务程序需要自行实现相应的配置读取和解析逻辑。
除了在服务启动时需要读取配置外,有些行业领域要求服务应用可在服务运行过程中具有动态采用新配置的功能。这就要求开发人员编写更复杂的逻辑以保证服务进程在运行过程中可在适当的时机,重新读取配置并采用新配置,同时又必须保证新配置的采用过程不会导致服务进程不稳定甚至崩溃。动态读取配置的时机可有多种方式,较简单的方法比如,通过向服务进程发送某个特定信号,服务进程接到信号后重新读取配置,然后根据新配置设置服务环境并继续提供服务。
在SMF中,配置被称为属性(property),配置值称为属性值(property value)。利用svccfg(1M)工具可以设置服务的属性和属性值。服务配置缺省保存在SMF全局资源库/etc/svc/repository.db中。 SMF的svc.configd进程负责存取资源库。当其他SMF进程或工具需要配置信息时,可以通过连接svc.configd进程从资源库中获取配置,同样也通过svc.configd将配置存入资源库。用户服务程序也可以通过使用libscf.so提供的API与svc.configd进程相连接,从资源库中存取服务配置。
配置SMF服务最简单的方式是使用svccfg(1M)工具。svccfg(1M)工具是一个交互式的配置修改工具,它包含许多简单实用的命令(具体命令可以在进入svccfg后使用help查询)。比如常用命令有:
另外,SMF还支持配置快照(snapshot)功能,通过它可以保留服务在某一时刻的属性配置,并在需要的时候可通过revert将当前配置恢复为某一时刻的快照配置。快照相关的命令包括listsnap,selectsnap和revert。
无论采用何种方式修改了当前服务的配置,这些修改只影响到资源库而非正在运行的服务进程。如果要求当前服务能够马上应用新配置,则用户在退出svccfg(1M)工具后需要使用svcadm refresh命令指示相关服务重新读取配置并采用新配置。
在SMF框架中,start和stop方法是服务必须提供的,这是由于SMF需要调用start和stop方法来启动和停止相应的服务。而refresh方法则不是必要的,前提是用户服务不需要动态刷新启用新配置属性。如果用户服务需要动态刷新配置属性,则用户服务在定义时,必须提供服务refresh方法,而相应服务程序也必须实现适时重新读取配置的逻辑。
正如前文提到的,配置有多种存在形式,不同存在形式读取方法也不同。另外,实现SMF的refresh方法也有多种方式,比如通过信号(singal)通知服务进程读取最新配置就是一种简单实用的方法。本节下文将结合一个例子讲述如何利用libscf.so提供的API读取保存在SMF全局资源库中的配置,并通过信号方式通知服务进程重新读取配置,从而实现SMF所需的refresh方法。
是一个简单的程序myapp.c,它运行后将成为后台守护进程存在于系统中,并每间隔5秒钟向日志文件插入一行记录以报告自己的存在。虽然它实际上不向外提供任何服务,但该程序模拟了一般服务程序的设计结构和运行模式。即,程序运行后以守护进程形式存在于系统,程序头部有服务配置读取逻辑read_config()和服务初始化逻辑app_init(),中部使用sleep(5)模拟等待服务请求逻辑,通过向日志插入记录模拟服务请求处理逻辑,然后返回至循环开始处sleep(5)继续等待下一个服务请求等。只要在此结构上修改和扩充相应的逻辑就可以将此程序修改为一个真正的服务程序。
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 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
/*****************************************************************************/ /* myapp.c */ /*****************************************************************************/ #include #include #include #include #include #include #include #include #include #include /* global exit status code */ #define RUN_OK 0 #define CONFIG_ERROR 1 #define FATAL_ERROR 2 /* global variable declaration */ char filename[1025]; scf_handle_t *handle = NULL; scf_scope_t *sc = NULL; scf_service_t *svc = NULL; scf_propertygroup_t *pg = NULL; scf_property_t *prop = NULL; scf_value_t *value = NULL; /* function declaration */ int read_config(void); int app_init(void); void daemonize(void); void sig_usr(int signo); void destroy_scf(void); int main(int argc, char **argv) { FILE *fp; time_t t; /* Read the app configuration here. */ /* 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(filename,"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); /* setup proper file mask */ } /* read configurations in SMF repository */ int read_config(void) { memset(filename, 0, sizeof(filename)); /* connect to the current SMF global repository */ handle = scf_handle_create(SCF_VERSION); /* allocate scf resources */ sc = scf_scope_create(handle); svc = scf_service_create(handle); pg = scf_pg_create(handle); prop = scf_property_create(handle); value = scf_value_create(handle); /* if failed to allocate resources, exit */ if (handle == NULL || sc == NULL || svc == NULL || pg == NULL || prop == NULL || value == NULL) { destroy_scf(); return CONFIG_ERROR; } /* bind scf handle to the running svc.configd daemon */ if ((scf_handle_bind(handle)) == -1) { destroy_scf(); return CONFIG_ERROR; } /* get the scope of the localhost in the current repository */ if (scf_handle_get_scope(handle, SCF_SCOPE_LOCAL, sc) == -1) { destroy_scf(); return CONFIG_ERROR; } /* get the service of "application/myapp" within the scope */ if (scf_scope_get_service(sc, "application/myapp", svc) == -1) { destroy_scf(); return CONFIG_ERROR; } /* get the property group of "myapp" within the given service */ if (scf_service_get_pg(svc, "myapp", pg) == -1) { destroy_scf(); return CONFIG_ERROR; } /* get the propery of "log_filename" within the given property group */ if (scf_pg_get_property(pg, "log_filename", prop) == -1) { destroy_scf(); return CONFIG_ERROR; } /* get the value of the given property */ if (scf_property_get_value(prop, value) == -1) { destroy_scf(); return CONFIG_ERROR; } /* retrieve the value as a string */ if (scf_value_get_astring(value, filename, sizeof(filename)) == -1) { destroy_scf(); return CONFIG_ERROR; } /* free all resources */ destroy_scf(); return RUN_OK; /* OK */ } /* free all allocated scf resources */ void destroy_scf(void) { if (value != NULL) scf_value_destroy(value); if (prop != NULL) scf_property_destroy(prop); if (pg != NULL) scf_pg_destroy(pg); if (svc != NULL) scf_service_destroy(svc); if (sc != NULL) scf_scope_destroy(sc); if (handle != NULL) scf_handle_destroy(handle); } /* initialize application */ int app_init(void) { /* setup the SIGUSR1 signal handler */ signal(SIGUSR1, sig_usr); return RUN_OK; /* OK */ } /* signal handler for SIGUSR1 */ void sig_usr(int signo) { /* re-read the configuration when signal is received */ if (read_config() != RUN_OK) exit(CONFIG_ERROR); } |
在myapp.c程序中,read_config()就是读取保存在SMF全局资源库配置的过程。除了在main()过程第行服务启动时被调用外,还在sig_usr()过程中第行被调用。而sig_usr()过程在app_init()初始化过程中被设定为SIGUSR1信号的处理程序。这意味着,此服务在运行过程中只要收到SIGUSR1信号后,就会重新读取存在SMF资源库中的配置信息。
使用libscf.so的API读取SMF全局资源库/etc/svc/repository.db服务配置属性的一般流程如下图所示。
本例中,服务名字为application/myapp。属性组myapp内只有一个属性log_filename,它指定了日志文件的全路径文件名。由于此属性为字符型,所以使用scf_value_get_astring()将属性值value转换字符串置于filename。如果服务有更多的属性,可以多次执行循环1,甚至如果用户服务有多个属性组,可以多次执行循环2,直至将所有属性全部读出。
read_config()过程的第至118行逻辑是申请scf资源,其目的是先得到保存相关scf资源的内存。相应的destroy_scf()过程是释放scf资源。当配置读取完毕后,应将所有申请的scf资源释放。
本例只使用很少一部分libscf.so中的API调用,API调用列表可参考man libscf(3LIB)。API调用具体定义可以参考。有关SMF服务、属性组、属性等概念,以及SMF服务开发方面更多信息,请参阅Solaris Service Management Facility - Service Developer Introduction。
利用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为后台守护进程。
启动脚本定义了如何启动myapp服务,以及与SMF相应的返回值。本例启动脚本如所示。
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/smfdemo/myapp 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 manifest文件定义了SMF服务各属性。比如,定义服务名称、服务依赖关系、服务启动方法、服务停止方法、服务刷新方法、服务所需参数等。本例实现了refresh方法,相应的manifest文件如所示。
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 |
type='service' version='1'> grouping='require_all' restart_on='none' type='service'> name='start' exec='/export/home/smfdemo/myapp_smf.sh start' timeout_seconds='60' /> name='stop' exec=':kill' timeout_seconds='60' /> name='refresh' exec=':kill -8' timeout_seconds='60' /> |
本列中refresh方法执行变量被设为“:kill -8”,通过kill命令向服务发送SIGUSR1信号。与myapp.c中read_config()逻辑相对应,manifest文件中创建了 myapp属性组(property group)。它包含一个名为log_filename的字符型属性,初始值设为/tmp/myapp.log。有关manifest文件编写详细信息,请参看Solaris Service Management Facility - Service Developer Introduction。
至此,使用libscf.so实现服务refresh方法的所有工作全部完成。下节中讲述如何将把myapp部署为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 |
假设本例中开发目录和所有文件都位于/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部署成功,根据log_filename初始值设定,可以在/tmp目录下找到myapp.log文件。使用/usr/bin/tail -f /tmp/myapp.log命令可以查看日志输出。至此,myapp已经部署完毕。
测试的目的是确认myapp的refresh方法可以正常工作。方法是通过svccfg(1M)命令将myapp服务log_filename属性值从 /tmp/myapp.log改为/tmp/myapp_new.log。然后用svcadm refresh命令通知myapp服务重读配置。如果在/tmp目录下新生成myapp_new.log,则说明refresh方法成功了。具体步骤如下:
1. 确认myapp已经启动并处于online状态,并/tmp/myapp.log每隔5秒就有内容输出。
# /usr/bin/svcs application/myapp STATE STIME FMRI online 10:53:57 svc:/application/myapp:default # /usr/bin/tail -f /tmp/myapp.log myapp is running at Fri Mar 10 10:57:12 2006 myapp is running at Fri Mar 10 10:57:17 2006 myapp is running at Fri Mar 10 10:57:22 2006 myapp is running at Fri Mar 10 10:57:27 2006 myapp is running at Fri Mar 10 10:57:32 2006 myapp is running at Fri Mar 10 10:57:37 2006 ^C |
2. 使用svccfg(1M)修改myapp服务myapp属性组中log_filename属性。
# /usr/sbin/svccfg svc:> select application/myapp svc:/application/myapp> listprop general framework general/single_instance boolean true milestone dependency milestone/entities fmri svc:/milestone/multi-user milestone/grouping astring require_all milestone/restart_on astring none milestone/type astring service start method start/exec astring "/export/home/smfdemo/myapp.sh start" start/timeout_seconds count 60 start/type astring method stop method stop/exec astring :kill stop/timeout_seconds count 60 stop/type astring method refresh method refresh/exec astring ":kill -8" refresh/timeout_seconds count 60 refresh/type astring method myapp application myapp/log_filename astring /tmp/myapp.log svc:/application/myapp> setprop myapp/log_filename = "/tmp/myapp_new.log" svc:/application/myapp> listprop general framework general/single_instance boolean true milestone dependency milestone/entities fmri svc:/milestone/multi-user milestone/grouping astring require_all milestone/restart_on astring none milestone/type astring service start method start/exec astring "/export/home/smfdemo/myapp.sh start" start/timeout_seconds count 60 start/type astring method stop method stop/exec astring :kill stop/timeout_seconds count 60 stop/type astring method refresh method refresh/exec astring ":kill -8" refresh/timeout_seconds count 60 refresh/type astring method myapp application myapp/log_filename astring /tmp/myapp_new.log svc:/application/myapp> quit |
3. 使用svcadm refresh命令通知myapp服务重读配置并启用新配置。
# /usr/sbin/svcadm refresh application/myapp # /usr/bin/svcs application/myapp STATE STIME FMRI online 11:13:35 svc:/application/myapp:default |
4. 检验myapp已经采用新配置。
# /usr/bin/tail -f /tmp/myapp_new.log myapp is running at Fri Mar 10 11:14:35 2006 myapp is running at Fri Mar 10 11:14:40 2006 myapp is running at Fri Mar 10 11:14:45 2006 myapp is running at Fri Mar 10 11:14:50 2006 myapp is running at Fri Mar 10 11:14:55 2006 myapp is running at Fri Mar 10 11:15:00 2006 ^C |
至此,测试myapp服务refresh方法已经成功。
Solaris 10操作系统是Sun公司最新的下一代操作系统,包含了600多项革新技术,SMF技术就是其中之一。通过使用SMF技术,系统中所有的服务可以在一个统一而强大的平台中进行配置和管理。通过将用户配置存放于SMF资源库,用户可以利用SMF配置工具svccfg(1M)方便地维护服务配置。另外,libscf.so所提供的API也可以使服务程序省去大部分复杂的配置读取和解析逻辑。
1.
2.
3.
4. Solaris 10操作系统/usr/share/lib/xml/dtd/service_bundle.dtd文件
5. Solaris 10操作系统上,以下man页面:
libscf(3LIB)
svc.startd(1M)
svcadm(1M)
svccfg(1M)
svcprop(1)
svcs(1)