Linux 計算節點上 HPC Pack 執行篩選器簡介
LINUX 計算節點上的執行篩選器是在 HPC 的 6 月 QFE 發行中引進。 它可讓叢集管理員在Linux計算節點的不同作業/工作執行階段內,插入自定義腳本(根目錄下)。
使用執行篩選的兩個典型案例:
在整合 Active Directory 的 Linux 節點上,使用者名稱格式的格式不同。 使用執行篩選器,系統管理員可以在執行作業/工作之前自定義使用者名稱格式。 例如,將使用者名稱從 「domain\user」 格式轉譯為 「domain.user」 格式
通常每個使用者在SMB共用上都有自己的主資料夾。 使用執行篩選器,系統管理員可以提供腳本來掛接 SMB 共用與使用者的網域帳戶,讓作業/工作可以在共用中存取自己的數據,並在工作結束之前進行清除。
啟用執行篩選
您可以藉由在 /opt/hpcnodemanager/filters
底下新增腳本檔案,來啟用Linux節點上的執行篩選。 HPC Pack 目前支援:
OnJobTaskStart.sh
:當第一個工作分派至Linux節點時,將會立即執行此腳本。 從目前實作開始,此腳本可能會多次執行,以防工作是參數式掃掠工作。 您可能需要處理這種情況。 系統管理員可以利用此腳本來執行節點準備,例如為使用者掛接共用。OnTaskStart.sh
:當後續工作分派至Linux節點時,將會執行此腳本。 系統管理員可以利用此腳本來自定義工作執行環境,例如 WorkDir。OnJobEnd.sh
:當作業在Linux節點上結束時,將會執行此腳本。 系統管理員可以利用此腳本進行一些清除。
請注意,由於排程原則/網路問題/重試,可能會針對相同作業或工作觸發多次篩選。
自定義執行篩選
執行篩選條件的輸入
所有執行篩選條件的輸入都是透過 JSON 數據格式的標準輸入。 以下是範例:
OnJobTaskStart.sh
的範例輸入:{ "m_Item1": { "JobId": 9299, "ResIds": [ 141 ], "TaskId": 170355 }, "m_Item2": { "affinity": [ 1 ], "commandLine": "echo specialword1", "environmentVariables": { "CCP_CLUSTER_NAME": "LN11-RH71-HN1", "CCP_COREIDS": "0", "CCP_EXCLUSIVE": "False", "CCP_ISADMIN": "1", "CCP_JOBID": "9299", "CCP_JOBNAME": "Test Basic OnJobTaskStart", "CCP_JOBTYPE": "Batch", "CCP_MPI_NETMASK": "10.156.60.0/255.255.252.0", "CCP_NODES": "1 LN11-RH71-LN2 1", "CCP_NODES_CORES": "1 LN11-RH71-LN2 1", "CCP_NUMCPUS": "1", "CCP_OWNER_SID": "S-1-5-21-1645912939-3214980066-801894016-500", "CCP_REQUIREDNODES": "", "CCP_RERUNNABLE": "False", "CCP_RETRY_COUNT": "0", "CCP_RUNTIME": "2147483647", "CCP_SERVICEREGISTRATION_PATH": "\\\\LN11-RH71-HN1\\HpcServiceRegistration", "CCP_TASKID": "1", "CCP_TASKINSTANCEID": "0", "CCP_TASKSYSTEMID": "170355", "HPC_RUNTIMESHARE": "\\\\LN11-RH71-HN1\\Runtime$" }, "stderr": null, "stdin": null, "stdout": null, "taskRequeueCount": 0, "workingDirectory": null }, "m_Item3": "hpclnpr11\\Administrator", "m_Item4": "Password", "m_Item5": null, "m_Item6": null }
m_Item1
的內容包含基本作業和工作標識符資訊。m_item2
的內容包含更詳細的作業資訊,說明作業的執行方式,以及哪些參數。environmentVariables
區塊也包含使用者定義Environment Variables
,其定義於作業/工作:
-
m_Item3
和m_Item4
的內容是作業Runas
使用者的用戶和密碼。 當您的執行篩選器使用這些資訊時,請小心(特別是密碼)。
OnTaskStart.sh
的範例輸入:{ "m_Item1": { "JobId": 11274, "ResIds": [ 205 ], "TaskId": 206059 }, "m_Item2": { "affinity": [ 1 ], "commandLine": "echo specialword1", "environmentVariables": { "CCP_CLUSTER_NAME": "LN11-RH71-HN1", "CCP_COREIDS": "0", "CCP_EXCLUSIVE": "False", "CCP_JOBID": "11274", "CCP_JOBNAME": "", "CCP_JOBTYPE": "Batch", "CCP_MPI_NETMASK": "10.156.60.0/255.255.252.0", "CCP_NODES": "1 LN11-RH71-LN1 1", "CCP_NODES_CORES": "1 LN11-RH71-LN1 1", "CCP_NUMCPUS": "1", "CCP_OWNER_SID": "S-1-5-21-1645912939-3214980066-801894016-500", "CCP_REQUIREDNODES": "", "CCP_RERUNNABLE": "False", "CCP_RETRY_COUNT": "0", "CCP_RUNTIME": "2147483647", "CCP_SERVICEREGISTRATION_PATH": "\\\\LN11-RH71-HN1\\HpcServiceRegistration", "CCP_TASKID": "2", "CCP_TASKINSTANCEID": "0", "CCP_TASKSYSTEMID": "206059", "HPC_RUNTIMESHARE": "\\\\LN11-RH71-HN1\\Runtime$" }, "stderr": null, "stdin": null, "stdout": null, "taskRequeueCount": 0, "workingDirectory": null } }
OnJobEnd.sh
的範例輸入:{ "JobId": 9299, "JobInfo": null, "ResIds": [ 141 ] }
執行篩選的輸出
針對 OnJobTaskStart.sh
和 OnTaskStart.sh
篩選,輸出應該透過標準輸出傳遞回正式 JSON 格式的 nodemanager。 具有新作業信息的輸出會影響作業行為。 而 OnJobEnd.sh
的標準輸出不會影響作業行為。 除了標準輸出之外,OnJobTaskStart.sh
和 OnTaskStart.sh
篩選條件應該結束,0
表示成功執行,否則 nodemanager 會略過啟動作業/工作,並將錯誤傳回排程器。 您可以在記錄檔
自定義執行篩選
若要自定義執行篩選條件,請取得並剖析從腳本中的標準輸入傳送的 JSON 數據、使用資料來執行某些函式,然後透過標準輸出撰寫 JSON,以進行 OnJobTaskStart.sh
和 OnTaskStart.sh
篩選。 例如,我們在這裡使用 命令設定 OnJobTaskStart.sh
篩選,sed –s 's/specialword/OnJobTaskStart/g'
,命令會採用 stdin JSON 輸入數據、以 specialword
取代字串 OnJobTaskStart
,並透過 stdout 輸出:
然後提交作業,以驗證篩選是否有效:
針對執行篩選失敗進行疑難解答
當作業失敗時,請檢查特定節點上的 nodemanager 記錄。 記錄會提供失敗執行篩選條件的結束代碼。 例如,在下列快照集中,JobStartFilter 失敗,結束代碼 127。
在執行篩選中新增記錄,並將輸出提供給檔案。
使用執行篩選器以網域使用者身分執行作業,併為不同的網域使用者掛接 SMB 共用
在 HPC 的 6 月 QFE 版本中,我們引進了 Linux 節點的執行篩選器(此功能未公開使用)。 您可以在 HPC Linux nodemanager 的安裝資料夾 filters
中新增名為 /opt/hpcnodemanager
的資料夾,並在 OnJobTaskStart.sh
資料夾中,將命名為 OnTaskStart.sh
、OnJobEnd.sh
或 filters
的攔截腳本來啟用執行篩選。 如需執行篩選的詳細資訊,請參閱 適用於 Linux 的 HPC Pack 執行篩選。
在此範例中,我們滿足下列案例:
- HPC Administrator 具有具有 Active Directory 整合式 Linux 節點的 HPC 叢集。
- 為了支援某些應用程式,他們會以自定義格式設定用戶名稱,例如,在Linux節點上透過smb.conf使用 winbind 或 sssd.conf 時使用 winbind 或 sssd.conf,因此希望 HPC 在這些 Linux 節點上以正確的網域使用者身分執行作業。
- 針對每個網域使用者,系統管理員已建立專用SMB共用,並希望這些共用可以對應掛接至每個使用者主目錄中的共享資料夾,因此使用者可以取用儲存在共用中的數據。
共用權限控制的知識:
- 如需如何在 Windows 中設定 SMB 共用的許可權控件,請參閱 管理共用資料夾的許可權
- 如需如何在Linux端控制SMB共用許可權,請參閱
man 8 mount
或man mount.cifs
設定步驟:
建立 SMB 共用,如案例所述,在此範例中,我們會在 sever
LN11-RH71-HN1
上建立共享資料夾,並將它命名為SmbShareDemo
。 然後,在不同的網域使用者的資料夾SmbShareDemo
中建立子目錄,例如,我們會為網域使用者建立資料夾mpiuser1
、mpiuser2
、mpiuser3
hpclnpr11\mpiuser1
、hpclnpr11\mpiuser2
、hpclnpr11\mpiuser3
等。我們會將網域使用者read/write
許可權授與對應存取共用資料夾的許可權,例如,授與資料夾hpclnpr11\mpiuser1
read\write
mpiuser1
許可權:準備執行篩選:
2.1 在 Linux 節點下,建立 filters 資料夾:
2.2 請使用
chmod 700 /opt/hpcnodemanager/filters
來限制執行篩選的許可權,並確定只有系統管理員(root 或 sudoers)可以檢視和修改執行篩選。2.3 複製範例腳本 ResolveUserName.py 和 ResolveUserNameAndDoMount.py(請參閱本文結尾),並將其複製到資料夾
/opt/hpcnodemanager/filters/
。2.4 建立 OnJobTaskStart 篩選
OnJobTaskStart.sh
,以呼叫 python 腳本ResolveUserNameAndDoMount.py
:腳本
ResolveUserNameAndDoMount.py
重複使用ResolveUserName.py
的邏輯來撰寫想要的使用者名稱,同時,如果尚未掛接共用,則會將共用命名//[SmbShareSever]/SmbShareDemo/[UserName]
掛接至使用者主目錄中非系統管理員使用者的共享資料夾。 (這裡[SmbShareSever]
是共享伺服器,也就是在此範例中LN11-RH71-HN1
,[UserName]
是作業以提交作業時所提供的使用者身分執行作業的名稱。2.5 修改
ResolveUserName.py
,並確定它組成正確的用戶名稱:2.6 修改
ResolveUserNameAndDoMount.py
請確定它可以正確匯入
ResolveUserName.py
,根據預設,它們必須位於相同的資料夾中以您環境中的 SMB 伺服器名稱取代
[SmbShareSever]
:根據預設,目錄會掛接file_mode
0755
和dir_mode0755
,請修改以符合您的需求:記錄設備可用來進行疑難解答,如腳本中的批註所示:
嘗試搭配 HPC 作業執行篩選:
3.1 檢查 Linux 節點的狀態、是否已針對
mpiuser1
掛接共用,以及是否可以使用網域使用者執行 bash 命令。3.2 使用工作
whoami
提交作業,並將 [資源] 選取至已設定執行篩選集的節點:3.3 檢查以使用者身分執行是否如預期般執行,並正確掛接共用:
若要讓篩選可供所有 Linux 節點使用,請將篩選複製到共用,並使用 clusrun 將篩選部署到共用中的所有 Linux 節點:
PS > clusrun /nodegroup:LinuxNodes cp -rf <SmbSharePath>/filters /opt/hpcnodemanager/
作為附錄的腳本
ResolveUserName.py
#!/usr/bin/env python # Hpc Execution Filter Sample - Compose Customized Active Directory User Name # Introduction: # When it's in an Active Directly integrated Linux environment, # it's necessary to compose right RunAs user with different settings, # such as: 'winbind seperator' set in /etc/samba/smb.conf for Winbind # or 're_expression' set in /etc/sssd/sssd.conf for SSSD. # to ensure right user is used when HPC run jobs. # # In this case, we compose RunAs user, for example: # composedUserName = "{0}.{1}".format(domainName, userName) when Winbind Seperator set to . delimiter # or In SSSD, when set re_expression = ((?P<domain>.+)\.(?P<name>[^\\\.@]+$)) # # Return codes: # 0 success # 1 incorrect invocation import json import sys def ComposeAdUserName(domainName, userName): """ Examples: composedUserName = "{0}@{1}".format(userName, domainName), when using userName@domainName """ composedUserName = "{0}.{1}".format(domainName, userName) return composedUserName def Main(): """The input is job execution context in json format.""" jsonData = json.loads(sys.stdin.readline()) """Get and compose user name, by default it's in domain\username format.""" composedUserName = jsonData["m_Item3"] runAsUserInfo = composedUserName.split('\\') if len(runAsUserInfo) == 2: domainName = runAsUserInfo[0] userName = runAsUserInfo[1] composedUserName = ComposeAdUserName(domainName, userName) """Set composedUserName.""" jsonData["m_Item3"] = composedUserName """Return the result through stdout""" print json.dumps(jsonData) sys.exit(0) if __name__ == '__main__': Main()
ResolveUserNameAndDoMount.py
#!/usr/bin/env python # Hpc Execution Filter Sample - Compose Customized Active Directory User Name and Do Mount For Non-Admin Users # This script reuse ResolveUserName.py to compose right Active Directory user name, # and do mount for Non-Admin users. # # The sample fulfils the following scenario: # Administrators use HPC Linux Support with Active Directory Integrated, # and provide SMB Shares with pattern //SmbShareBasePath/UserName for each Active Directory User to do data movement. # Administrators wish to ensure the shares can be mounted for different users. # In this script, the specific SMB share will be mounted to the share folder in each users' home directory, # with uid and gid set correspondingly, and file_mode and dir_mode both set to 755. # # Please notice this sample script will parse the password of users for mounting, # and be sure this aligns security policies before using it in production environments. # # Return codes: # This script follow command mount's return codes: # 0 success # 1 incorrect invocation or permissions # 2 system error (out of memory, cannot fork, no more loop devices) # 4 internal mount bug # 8 user interrupt # 16 problems writing or locking /etc/mtab # 32 mount failure # 64 some mount succeeded # For more about mount's return codes, please refer man 8 mount. import json import os import pwd import string import subprocess import sys import time import ResolveUserName """Define the constants.""" SmbShareBasePath = "//[SmbShareSever]/SmbShareDemo" def MountSmbShare(smbSharePath, targetPath, domainName, userName, password, uid, gid, fileMode="0755", dirMode="0755"): retCode = 0 if os.path.ismount(targetPath) == False: maxRetry = 3 while(maxRetry > 0): retCode = Run("mount -t cifs {0} {1} -o domain={2},username={3},password='{4}',uid={5},gid={6},file_mode={7},dir_mode={8}".format(smbSharePath, targetPath, domainName, userName, password, uid, gid, fileMode, dirMode)) """Check if succeeded, and skip the case when another process successfully mount the share.""" if retCode == 0 or os.path.ismount(targetPath): retCode = 0 break maxRetry = maxRetry - 1 time.sleep(1) return retCode """Run command facilities.""" if not hasattr(subprocess,'check_output'): def check_output(*popenargs, **kwargs): r"""Backport from subprocess module from python 2.7""" if 'stdout' in kwargs: raise ValueError('stdout argument not allowed, it will be overridden.') process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) output, unused_err = process.communicate() retcode = process.poll() if retcode: cmd = kwargs.get("args") if cmd is None: cmd = popenargs[0] raise subprocess.CalledProcessError(retcode, cmd, output=output) return output # Exception classes used by this module. class CalledProcessError(Exception): def __init__(self, returncode, cmd, output=None): self.returncode = returncode self.cmd = cmd self.output = output def __str__(self): return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) subprocess.check_output=check_output subprocess.CalledProcessError=CalledProcessError def Run(cmd,chk_err=True): retcode,out=RunGetOutput(cmd,chk_err) return retcode def RunGetOutput(cmd,chk_err=True): try: output=subprocess.check_output(cmd,stderr=subprocess.STDOUT,shell=True) except subprocess.CalledProcessError,e : if chk_err : Error('CalledProcessError. Error Code is ' + str(e.returncode) ) Error('CalledProcessError. Command result was ' + (e.output[:-1]).decode('latin-1')) return e.returncode,e.output.decode('latin-1') return 0,output.decode('latin-1') """End of run command facilities.""" """ Logging facilities can be removed from the script. Log can be used for trouble shooting, and remember to comment them out when performance is considered more important. """ LocalTime = time.localtime() ExecutionFilterSampleLogFile = "./ExecutionFilter_ResolveUserAndMount_%04u%02u%02u-%02u%02u%02u.log" % (LocalTime.tm_year, LocalTime.tm_mon, LocalTime.tm_mday, LocalTime.tm_hour, LocalTime.tm_min, LocalTime.tm_sec) def LogWithPrefix(prefix, message): t = time.localtime() t = "%04u/%02u/%02u %02u:%02u:%02u " % (t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec) t += prefix for line in message.split('\n'): line = t + line line = filter(lambda x : x in string.printable, line) try: with open(ExecutionFilterSampleLogFile, "a") as F : F.write(line.encode('ascii','ignore') + "\n") except IOError, e: pass def Log(message): LogWithPrefix("INFO: ", message) def Error(message): LogWithPrefix("ERROR: ", message) def Warn(message): LogWithPrefix("WARNING: ", message) """End of logging facilities.""" def Main(): retCode = 0 """The input is job execution context in json format.""" jsonData = json.loads(sys.stdin.readline()) try: """Get user name, by default it's in domain\username format.""" composedUserName = jsonData["m_Item3"] runAsUserInfo = composedUserName.split('\\') if len(runAsUserInfo) < 2: Error("Illegal input runAsUser: {0}, be sure the input is hpc job context in json format.".format(composedUserName)) sys.exit(1) domainName = runAsUserInfo[0] userName = runAsUserInfo[1] """Resolve right Active Directory user name.""" composedUserName = ResolveUserName.ComposeAdUserName(domainName, userName) """Query if the user is admin, and mount for Non-Admin users.""" isAdmin = "0" try: isAdmin = jsonData["m_Item2"]["environmentVariables"]["CCP_ISADMIN"] except KeyError: pass if isAdmin == "0": """Check whether user exists, touch user's home dir, and get user information.""" retCode = Run("mkhomedir_helper {0}".format(composedUserName)) if retCode != 0: Error("No such user: {0}, or home directory for this user cannot be used or generated properly.".format(composedUserName)) sys.exit(1) pwdInfo = pwd.getpwnam(composedUserName) uid = pwdInfo.pw_uid gid = pwdInfo.pw_gid homeDir = pwdInfo.pw_dir """Get password, please note the risk here.""" password = jsonData["m_Item4"] """Do mount for Non-Admin users.""" smbSharePath = "{0}/{1}".format(SmbShareBasePath, userName) targetPath = "{0}/share".format(homeDir) retCode = Run("mkdir -p {0}".format(targetPath)) if retCode != 0: Error("Cannot find and create mount target path: {0}".format(targetPath)) sys.exit(1) retCode = MountSmbShare(smbSharePath, targetPath, domainName, userName, password, uid, gid) """Set composedUserName.""" jsonData["m_Item3"] = composedUserName except KeyError: """Please check whether the script is used correctly.""" Error("Please check whether the script is used correctly, and ensure it get right format job context json.") retCode = 1 """Return the result through stdout.""" print json.dumps(jsonData) #Log("ExecutionFitler finished with retCode:{0}".format(retCode)) sys.exit(retCode) if __name__ == '__main__': Main()