Menggunakan Filter Eksekusi Linux

Pengantar Filter Eksekusi Paket HPC pada Simpul Komputasi Linux

Filter eksekusi pada simpul komputasi Linux diperkenalkan dalam rilis QFE HPC Juni. Ini memungkinkan admin kluster untuk melakukan plugin skrip yang disesuaikan untuk dijalankan (di bawah root) pada simpul komputasi Linux selama tahap eksekusi pekerjaan/tugas yang berbeda.

Dua skenario umum menggunakan filter eksekusi:

  • Pada simpul linux dengan Direktori Aktif terintegrasi, format nama pengguna dalam format yang berbeda. Dengan filter eksekusi, admin dapat menyesuaikan format nama pengguna sebelum pekerjaan/tugas dijalankan. Misalnya, terjemahkan nama pengguna dari format "domain\user" ke format "domain.user"

  • Biasanya setiap pengguna memiliki folder utamanya sendiri pada berbagi SMB. Dengan filter eksekusi, admin dapat menyediakan skrip untuk memasang berbagi SMB dengan akun domain pengguna sehingga pekerjaan/tugas dapat mengakses datanya sendiri di berbagi dan melakukan pembersihan sebelum pekerjaan berakhir.

Aktifkan Filter Eksekusi

Filter eksekusi pada Simpul Linux dapat diaktifkan dengan menambahkan file skrip di bawah /opt/hpcnodemanager/filters. Saat ini HPC Pack mendukung:

  • OnJobTaskStart.sh: Skrip ini akan segera dijalankan ketika tugas pertama dikirim ke Simpul Linux. Pada implementasi saat ini, skrip ini mungkin dijalankan beberapa kali jika tugas tersebut adalah tugas pembersihan parametrik. Anda mungkin perlu menangani situasi ini. Admin dapat memanfaatkan skrip ini untuk melakukan persiapan simpul seperti memasang berbagi untuk pengguna.

  • OnTaskStart.sh: Skrip ini akan dijalankan ketika tugas tindak lanjut dikirim ke Simpul Linux. Admin dapat memanfaatkan skrip ini untuk menyesuaikan lingkungan eksekusi tugas seperti WorkDir.

  • OnJobEnd.sh: Skrip ini akan dijalankan ketika pekerjaan berakhir pada Simpul Linux. Admin dapat memanfaatkan skrip ini untuk melakukan pembersihan.

Harap dicatat bahwa filter dapat dipicu beberapa kali untuk pekerjaan atau tugas yang sama karena kebijakan penjadwalan/masalah jaringan/percobaan ulang.

Kustomisasi Filter Eksekusi

Input untuk filter eksekusi

Input untuk semua filter eksekusi melalui input standar dalam format data JSON. Berikut ini adalah contohnya:

  1. Contoh input untuk 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
    }
    
    • Konten untuk m_Item1 berisi informasi id tugas dan pekerjaan dasar.

    • Konten untuk m_item2 berisi informasi pekerjaan yang lebih rinci yang menjelaskan bagaimana pekerjaan akan dijalankan, dan dengan parameter mana. Blok environmentVariables juga berisi Environment Variables yang ditentukan pengguna yang ditentukan dalam pekerjaan/tugas:

    Envrs

    • Konten untuk m_Item3 dan m_Item4 adalah pengguna dan kata sandi pengguna Runas pekerjaan. Harap berhati-hatilah ketika filter eksekusi Anda menggunakan informasi ini (terutama kata sandi).
  2. Contoh input untuk 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
        }
    }
    
  3. Contoh input untuk OnJobEnd.sh:

    {
        "JobId": 9299,
        "JobInfo": null,
        "ResIds": [
            141
        ]
    }
    

Output filter eksekusi

Untuk filter OnJobTaskStart.sh dan OnTaskStart.sh, output harus diteruskan melalui output standar kembali ke nodemanager dalam format JSON formal. Output dengan informasi pekerjaan baru akan memengaruhi perilaku pekerjaan. Dan output standar OnJobEnd.shtidak akan memengaruhi perilaku pekerjaan. Selain output standar, filter OnJobTaskStart.sh dan OnTaskStart.sh harus keluar dengan kode keluar 0 untuk menunjukkan keberhasilan eksekusi, jika tidak, nodemanager akan melewati memulai pekerjaan/tugas dan mengembalikan kesalahan kepada penjadwal. Kode kesalahan dapat dilihat di file log nodemanager.txt, silakan lihat bagian Memecahkan Masalah kegagalan Filter Eksekusi untuk detailnya.

Mengkustomisasi filter eksekusi

Untuk menyesuaikan filter eksekusi, silakan dapatkan dan uraikan data JSON yang ditransfer dari input standar dalam skrip, gunakan data untuk memenuhi beberapa fungsi, lalu buat output JSON melalui output standar untuk filter OnJobTaskStart.sh dan OnTaskStart.sh. Misalnya, di sini kita mengatur filter OnJobTaskStart.sh dengan perintah, sed –s 's/specialword/OnJobTaskStart/g', perintah akan mengambil data input JSON stdin, mengganti string specialword dengan OnJobTaskStart dan output melalui stdout:

OnJobTaskStart

Kemudian kirim pekerjaan untuk memvalidasi apakah filter berfungsi:

Newjob

Newjob

Viewjob

Memecahkan masalah kegagalan Filter Eksekusi

Ketika pekerjaan gagal, periksa log nodemanager pada simpul tertentu. Log akan memberikan kode keluar untuk filter eksekusi yang gagal. Misalnya, dalam rekam jepret berikut, JobStartFilter gagal dengan kode keluar 127.

Log

Tambahkan pengelogan di filter eksekusi, dan berikan output ke file.

Menggunakan Filter Eksekusi untuk Menjalankan Pekerjaan sebagai Pengguna Domain dan Memasang Berbagi SMB untuk Pengguna Domain yang Berbeda

Dalam rilis QFE HPC Juni, kami memperkenalkan Filter Eksekusi untuk Simpul Linux (Fitur ini tidak tersedia untuk umum). Filter eksekusi dapat diaktifkan dengan menambahkan folder bernama filters di folder penginstalan HPC Linux nodemanager /opt/hpcnodemanager, dan skrip hook yang menamai OnJobTaskStart.sh, OnTaskStart.sh, atau OnJobEnd.sh di folder filters. Untuk informasi selengkapnya tentang filter eksekusi, silakan lihat Filter Eksekusi Paket HPC untuk Linux.

Dalam sampel ini, kami memenuhi skenario berikut:

  • Administrator HPC memiliki kluster HPC dengan Simpul Linux terintegrasi Direktori Aktif.
  • Untuk mendukung beberapa aplikasi, mereka mengatur nama pengguna dalam format yang disesuaikan, misalnya "domain.username" pada simpul Linux melalui smb.conf saat menggunakan winbind atau sssd.conf saat menggunakan SSSD, dan dengan demikian ingin HPC menjalankan pekerjaan sebagai pengguna domain yang tepat pada simpul Linux ini.
  • Untuk setiap pengguna domain, administrator telah membuat berbagi SMB khusus dan berharap berbagi ini dapat dipasang ke folder berbagi di direktori beranda setiap pengguna dengan sesuai, dan dengan demikian pengguna dapat menggunakan data yang mereka simpan di berbagi.

Pengetahuan untuk kontrol izin berbagi:

  • Untuk cara mengatur kontrol izin berbagi SMB di Windows, lihat Mengelola Izin untuk Folder Bersama
  • Untuk bagaimana izin berbagi SMB dikontrol di sisi Linux, lihat man 8 mount atau man mount.cifs

Langkah-langkah Konfigurasi:

  1. Tetapkan berbagi SMB seperti skenario yang dijelaskan, dalam sampel ini kami membuat folder berbagi di sever LN11-RH71-HN1, menam SmbShareDemo. Lalu buat subdirektori di folder SmbShareDemo untuk pengguna domain yang berbeda, misalnya kita membuat folder mpiuser1, mpiuser2, mpiuser3 untuk pengguna domain hpclnpr11\mpiuser1, hpclnpr11\mpiuser2, hpclnpr11\mpiuser3 dan dll. Kami memberikan pengguna domain izin read/write untuk mengakses folder berbagi secara sesuai, misalnya memberikan izin hpclnpr11\mpiuser1read\write ke folder mpiuser1:

    Berbagi

  2. Siapkan Filter Eksekusi:

    2.1 Di bawah simpul Linux, buat folder filter:

    FilterFolder

    2.2 Harap gunakan chmod 700 /opt/hpcnodemanager/filters untuk membatasi izin filter eksekusi, dan pastikan hanya admin (root atau sudoers) yang dapat melihat dan memodifikasi filter eksekusi.

    2.3 Salin contoh skrip ResolveUserName.py dan ResolveUserNameAndDoMount.py (Lihat di akhir artikel ini), dan salin ke folder /opt/hpcnodemanager/filters/.

    2.4 Buat filter OnJobTaskStart OnJobTaskStart.sh untuk memanggil skrip python ResolveUserNameAndDoMount.py:

    OnJobTaskStart

    Skrip ResolveUserNameAndDoMount.py menggunakan kembali logika ResolveUserName.pyuntuk menyusun nama pengguna yang diinginkan, dan pada saat yang sama, skrip akan memasang penamaan berbagi //[SmbShareSever]/SmbShareDemo/[UserName] ke folder berbagi di direktori beranda pengguna untuk pengguna non-administrator jika berbagi belum dipasang. (Di sini [SmbShareSever] adalah sever berbagi, yaitu LN11-RH71-HN1 dalam contoh ini, [UserName] adalah nama pekerjaan yang dijalankan sebagai pengguna yang disediakan ketika pekerjaan dikirimkan.)

    2.5 Ubah ResolveUserName.py, dan pastikan nama pengguna yang tepat:

    ResolveUserName

    2.6 Mengubah ResolveUserNameAndDoMount.py

    • Pastikan dapat mengimpor ResolveUserName.py dengan benar, secara default mereka harus berada di folder yang sama

    • Ganti [SmbShareSever] dengan nama sever SMB di lingkungan Anda:

      SmbShareSever

    • Secara default, direktori akan dipasang dengan file_mode 0755 dan dir_mode 0755, silakan lakukan modifikasi yang menyelaraskan kebutuhan Anda:

      mountSmbShare

    • Fasilitas pengelogan dapat digunakan untuk pemecahan masalah, karena komentar dalam skrip:

      LogWithPrefix

  3. Coba Filter Eksekusi dengan Pekerjaan HPC:

    3.1 Periksa status simpul Linux, apakah berbagi yang dipasang untuk mpiuser1, dan apakah perintah bash dapat dijalankan menggunakan pengguna domain.

    Pengguna

    3.2 Kirim pekerjaan dengan tugas whoami, dan Pilih Sumber Daya ke simpul dengan set filter eksekusi:

    SubmitJob

    SubmitJob

    3.3 Periksa apakah eksekusi sebagai pengguna seperti yang diharapkan, dan berbagi dipasang dengan benar:

    Hasil

    Hasil

  4. Untuk membuat filter tersedia untuk semua node Linux, salin filter ke berbagi, dan sebarkan filter ke semua node Linux dari berbagi menggunakan clusrun:

    PS > clusrun /nodegroup:LinuxNodes cp -rf <SmbSharePath>/filters  /opt/hpcnodemanager/
    

Skrip sebagai lampiran

  • 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()