nss_slurm是一个可选的NSS插件,它允许计算节点上的作业通过本地slurmstepd进程进行passwd和group解析,而不是通过其他基于网络的服务,如LDAP、SSSD或NSLCD。
在集群上启用时,对于每个作业,作业的用户将拥有完整的struct passwd信息——用户名、uid、主gid、gecos信息、主目录和shell——作为每个步骤启动的一部分安全发送,并缓存在slurmstepd进程中。然后,该信息将通过getpwuid()/getpwnam()/getpwent()系统调用提供给该步骤启动的任何进程。
对于组信息(来自getgrgid()/getgrnam()/getgrent()系统调用),将提供struct group的一个简短视图。在给定流程中,响应将只包含用户所属的组,但只将用户本身列为成员。不提供组成员的完整列表。
1. NSS Slurm配置
nss_slurm有一个可选的配置文件-/etc/nss_slurm.conf。仅在以下情况下才需要此配置文件:
a.节点的主机名与NodeName不匹配,在这种情况下,您必须显式设置NodeName选项。
b.SlurmdSpoolDir与Slurm的 /var/spool/slurmd的默认位置不匹配 ,在这种情况下也必须提供它。
NodeName和SlurmdSpoolDir是目前唯一支持的配置选项。
2. 初始测试
在节点上直接启用NSS Slurm之前,应在新启动的作业步骤中使用-s slurm选项来验证其余设置是否已成功完成。 getent的-s选项允许它查询特定数据库 - 即使默认情况下尚未通过系统的nsswitch.conf启用它。 请注意,nss_slurm仅响应作业步骤本身内的进程的请求 - 您必须在作业步骤中启动getent命令以查看返回的任何数据。
相关测试命令:
srun getent -s slurm passwd
srun getent -s slurm group
3. NSS配置
启用nss_slurm非常简单,只需将slurm添加到/etc/nsswitch.conf中的passwd和group数据库即可。建议首先列出slurm,因为顺序(从左到右)决定查询NSS数据库的顺序,这确保slurm在将查询提交给其他源之前能够处理请求。
一旦启用,通过启动getent查询来测试它
相关测试命令:
srun getent passwd tim
srun getent group projecta
步骤:
进入源码contribs/nss_slurm/ 目录执行make && make install
建立软链接:ln -s /opt/gridview/slurm/lib/libnss_slurm.so.2 /usr/lib64/libnss_slurm.so.2
在slurm.conf中设置 LaunchParameters=enable_nss_slurm并重新启动slurmctld
启用后,可以在计算节点上使用scontrol getent命令打印与该节点上的作业步骤关联的所有用户的passwd和group信息。
4. 限制
nss_slurm只返回给定作业步骤中进程的结果。 它不会返回这些步骤之外的进程的任何结果,例如系统监视,节点运行状况检查,prolog或epilog脚本以及相关的节点系统进程。
nss_slurm并不意味着完全替代LDAP之类的网络目录服务,而是作为一种从这些系统中删除负载的方法,以提高大规模作业启动的性能。它通过删除“惊群现象”来实现这一点,该现象表现为如果一个大型作业的所有任务同时发出查找请求(通常是为了查询与用户本身相关的信息,这是NSS Slurm能够提供的唯一信息),并压倒底层目录服务。
5. 源码分析
综和源码分析过程得出如下结论:
(1)srun提交作业时会从slurmctld查询用户相关信息存入作业,而slurmctld通过查询密码数据库(本地密码文件 /etc/passwd和/etc/group或NIS或LDAP)中获取用户相关信息。
(2)计算节点slurmd创建作业步运行作业时,执行scontrol getent 和_nss_slurm_getpwnam_r/_nss_slurm_getgrnam_r相关命令会跟作业步进程通信,通过作业步进程从作业中获取用户相关信息。
5.1 nss_slurm插件中获取用户信息
获取passwd信息过程:
_nss_slurm_getpwnam_r/_nss_slurm_getpwuid_r/_nss_slurm_getpwent_r
(contribs\nss_slurm\libnss_slurm.c)
_internal_getpw
_pw_internal
stepd_available//获取作业步链表,并进行遍历
stepd_connect//打开和作业步的连接
stepd_getpw//跟作业步通信,获取作业步保存的user信息(slurm-19.05.1-2\src\common\stepd_api.c)
REQUEST_GETPW//通信消息类型
获取group组信息过程类似:
_nss_slurm_getgrnam_r/_nss_slurm_getgrgid_r/_nss_slurm_getgrent_r
_internal_getgr
_gr_internal
stepd_available
stepd_connect
stepd_getgr
REQUEST_GETGR//通信消息类型
5.2 scontrol getent node过程
main(slurm-19.05.1-2\src\scontrol\scontrol.c)
_process_command
scontrol_getent(src\scontrol\info_job.c)
stepd_available
stepd_connect
stepd_getpw(REQUEST_GETPW)
stepd_getgr(REQUEST_GETGR)
以上可以看出用户的passwd和group信息都是从作业步获取。
5.3 作业步守护进程响应请求过程
struct io_operations msg_socket_ops = {
.readable = &_msg_socket_readable,
.handle_read = &_msg_socket_accept
};
源码调用过程,作业步处理IO事件使用了REACTOR模型:
main(slurm-19.05.1-2\src\slurmd\slurmstepd\slurmstepd.c)
msg_thr_create(src\slurmd\slurmstepd\req.c)
_domain_socket_create//创建一个命名的unix域监听套接字
fd_set_nonblocking//设置非阻塞
eio_obj_create(fd, &msg_socket_ops, (void *)job);//创建eio对象,msg_socket_ops里面保存着事件处理回调函数
_msg_socket_accept//IO事件触发后被调用
_handle_request(slurm-19.05.1-2\src\slurmd\slurmstepd\req.c)
case REQUEST_GETPW:
_handle_getpw//接受请求,从stepd_step_rec_t *job中获取用户信息发出
job->msg_handle = eio_handle_create(0);//创建job->msg_handle
eio_new_initial_obj(job->msg_handle, eio_obj);//将eio对象添加到job->msg_handle,注册
_msg_thr_internal//创建内部消息处理线程
eio_handle_mainloop
while(1)//轮训处理
_poll_setup_pollfds//设置fd
_poll_internal//IO多路复用,同步事件分离器
_poll_dispatch//事件分发器
_poll_handle_event
(*obj->ops->handle_read) (obj, objList);//调用_msg_socket_accept
typedef struct {
...
uid_t uid; /* user id for job */
char *user_name;
/* fields from the launch cred used to support nss_slurm */
char *pw_gecos;
char *pw_dir;
char *pw_shell;
gid_t gid; /* group ID for job */
int ngids; /* length of the following gids array */
char **gr_names;
gid_t *gids; /* array of gids for user specified in uid */
...
eio_handle_t *msg_handle; /* eio handle for the message thread */
...
} stepd_step_rec_t;
以上分析过程可以看出,用户的信息是事先保存在计算节点上运行的作业步之内的,而这个信息是何时何处存入作业的需要进一步分析作业创建流程。
5.4 作业步创建过程
main(slurm-19.05.1-2\src\slurmd\slurmstepd\slurmstepd.c)
_init_from_slurmd(STDIN_FILENO, argv, &cli, &self, &msg)//从slurmd接收作业参数
safe_read(sock, incoming_buffer, len)//前面接收各种信息,包括作业步类型LAUNCH_TASKS,直到这一步
unpack_msg//解包,设置msg
job = _step_setup(cli, self, msg)//设置stepd_step_rec_t *job
mgr_launch_tasks_setup
stepd_step_rec_create
job->uid = (uid_t) msg->uid;
job->gid = (gid_t) msg->gid;
job->user_name = xstrdup(msg->user_name);
_slurm_cred_to_step_rec(msg->cred, job);
//该函数内部设置stepd_step_rec_t* job的用户的passwd和group信息,
//包括user_name/pw_gecos/pw_dir/pw_shell/ngids/gids/gr_names,相关信息源于msg->cred,而msg从slurmd获得。
5.5 slurmd处理消息直到创建作业步过程
main(slurm-19.05.1-2\src\slurmd\slurmd\slurmd.c)
_msg_engine
_handle_connection
_service_connection
slurmd_req(msg)(src\slurmd\slurmd\req.c)
_rpc_launch_tasks(msg)(REQUEST_LAUNCH_TASKS)
_forkexec_slurmstepd(LAUNCH_TASKS)
_send_slurmstepd_init
//1.实际通信,与作业步_init_from_slurmd相对应
//2.发送的msg源于slurmd_req(msg)
msg.msg_type = REQUEST_LAUNCH_TASKS
msg.data = req;
pack_msg(&msg, buffer);
_pack_launch_tasks_request_msg(REQUEST_LAUNCH_TASKS)
slurm_cred_pack
_pack_cred
//1.该步骤打包用户的passwd和group信息,包括user_name/pw_gecos/pw_dir/pw_shell/ngids/gids/gr_names。
//2.此外还有很多其他cred信息,例如job_core_bitmap、cores_per_socket、job_hostlist等,跟本问题不相关,不再赘述
safe_write(fd, get_buf_data(buffer), len);
以上可以看出作业步的msg来源于slurmd接收到的msg,消息类型REQUEST_LAUNCH_TASKS。
5.6 分析srun以定位msg里面的cred信息何时打包
main
srun(slurm-19.05.1-2\src\srun\srun.c)
create_srun_job
allocate_nodes
_create_job_step(使用gdb调试确认这步获得了作业的cred)
create_job_step
launch_g_create_job_step
(*(ops.create_job_step))()
launch_p_create_job_step(src\plugins\launch\slurm\launch_slurm.c)
launch_common_create_job_step
slurm_step_ctx_create_timeout
slurm_job_step_create(该步跟slurmctld通讯获取cred信息,REQUEST_JOB_STEP_CREATE/RESPONSE_JOB_STEP_CREATE)
_launch_app
_launch_one_app
launch_g_step_launch
(*(ops.step_launch))(job, cio_fds, global_rc, step_callbacks, opt_local);
launch_p_step_launch
slurm_step_launch
launch.cred = ctx->step_resp->cred;
_launch_tasks
slurm_step_launch_add
launch.cred = ctx->step_resp->cred;
_launch_tasks
p *job->step_ctx->step_resp->cred这步的数据结构关系为:
typedef struct srun_job {
int fir_nodeid;
uint32_t jobid; /* assigned job id */
uint32_t stepid; /* assigned step id */
uint32_t node_offset; /* pack job node offset or NO_VAL */
...
slurm_step_ctx_t *step_ctx;
...
} srun_job_t;
typedef struct slurm_step_ctx_struct slurm_step_ctx_t;
struct slurm_step_ctx_struct {
uint16_t magic; /* magic number */
uint32_t job_id; /* assigned job id */
uint32_t user_id; /* user the job runs as */
job_step_create_request_msg_t *step_req;
job_step_create_response_msg_t *step_resp;
/* Used by slurm_step_launch() */
struct step_launch_state *launch_state;
uint16_t verbose_level;
};
typedef struct job_step_create_response_msg {
uint32_t def_cpu_bind_type; /* Default CPU bind type */
uint32_t job_step_id; /* assigned job step id */
char *resv_ports; /* reserved ports */
slurm_step_layout_t *step_layout;
slurm_cred_t *cred; /* slurm job credential */
dynamic_plugin_data_t *select_jobinfo; /* select opaque data type */
dynamic_plugin_data_t *switch_job; /* switch opaque data type */
uint16_t use_protocol_ver;
} job_step_create_response_msg_t;
typedef struct slurm_job_credential slurm_cred_t;
struct slurm_job_credential {
#ifndef NDEBUG
# define CRED_MAGIC 0x0b0b0b
int magic;
#endif
pthread_mutex_t mutex;
uint32_t jobid; /* Job ID associated with this cred */
uint32_t stepid; /* Job step ID for this credential */
uid_t uid; /* user for which this cred is valid */
gid_t gid; /* user's primary group id */
char *pw_name; /* username */
char *pw_gecos; /* user information */
char *pw_dir; /* home directory */
char *pw_shell; /* user program */
int ngids; /* number of extended group ids sent in
* credential. if 0, these will need to
* be fetched locally instead. */
gid_t *gids; /* extended group ids for user */
char **gr_names; /* array of group names matching gids */
uint64_t job_mem_limit;/* MB of memory reserved per node OR
* real memory per CPU | MEM_PER_CPU,
* default=0 (no limit) */
uint64_t step_mem_limit;
uint16_t core_array_size; /* core/socket array size */
uint16_t *cores_per_socket;
uint16_t *sockets_per_node;
uint32_t *sock_core_rep_count;
List job_gres_list; /* Generic resources allocated to JOB */
List step_gres_list; /* Generic resources allocated to STEP */
char *job_constraints; /* constraints in job allocation */
bitstr_t *job_core_bitmap;
uint16_t job_core_spec; /* Count of specialized cores */
uint32_t job_nhosts; /* count of nodes allocated to JOB */
char *job_hostlist; /* list of nodes allocated to JOB */
bitstr_t *step_core_bitmap;
time_t ctime; /* time of credential creation */
char *step_hostlist;/* hostnames for which the cred is ok */
uint16_t x11; /* x11 flags set on job allocation */
char *signature; /* credential signature */
uint32_t siglen; /* signature length in bytes */
};
以上可以看出在使用srun提交作业时,会查询管理节点slurmctld获取cred放入作业中。
5.7 slurmctld处理srun发过来的请求,获取cred信息打包发回
main(slurm-19.05.1-2\src\slurmctld\controller.c)
_slurmctld_rpc_mgr
_service_connection
slurmctld_req(src\slurmctld\proc_req.c)
case REQUEST_JOB_STEP_CREATE:
_slurm_rpc_job_step_create
step_create
_make_step_cred
slurm_cred_create
_slurm_cred_init//初始化enable_nss_slurm插件功能
slurm_getpwuid_r
getpwuid_r
gid_to_string
getgrgid_r
resp.msg_type = RESPONSE_JOB_STEP_CREATE
slurm_send_node_msg(msg->conn_fd, &resp);
以上可以看出用户相关信息由slurmctld通过getpwuid_r、getgrgid_r从密码数据库(本地密码文件 /etc/passwd和/etc/group或NIS或LDAP)中获得。