کلسٹر کے لیے اپنا آٹو اسکیلر کیسے بنائیں

ہیلو! ہم لوگوں کو بڑے ڈیٹا کے ساتھ کام کرنے کی تربیت دیتے ہیں۔ اپنے کلسٹر کے بغیر بگ ڈیٹا پر تعلیمی پروگرام کا تصور کرنا ناممکن ہے، جس پر تمام شرکاء مل کر کام کرتے ہیں۔ اس وجہ سے، ہمارے پروگرام میں ہمیشہ یہ ہوتا ہے 🙂 ہم اس کی ترتیب، ٹیوننگ اور انتظامیہ میں مصروف رہتے ہیں، اور لوگ براہ راست وہاں MapReduce جابز شروع کرتے ہیں اور Spark استعمال کرتے ہیں۔

اس پوسٹ میں ہم آپ کو بتائیں گے کہ ہم نے کلاؤڈ کا استعمال کرتے ہوئے اپنا آٹو اسکیلر لکھ کر ناہموار کلسٹر لوڈنگ کا مسئلہ کیسے حل کیا۔ Mail.ru کلاؤڈ سلوشنز.

مسئلہ

ہمارا کلسٹر ایک عام موڈ میں استعمال نہیں ہوتا ہے۔ ڈسپوزل انتہائی ناہموار ہے۔ مثال کے طور پر، عملی کلاسز ہیں، جب تمام 30 افراد اور ایک استاد کلسٹر میں جاتے ہیں اور اسے استعمال کرنا شروع کر دیتے ہیں۔ یا پھر، آخری تاریخ سے پہلے کے دن ہوتے ہیں جب بوجھ بہت بڑھ جاتا ہے۔ باقی وقت کلسٹر انڈر لوڈ موڈ میں کام کرتا ہے۔

حل # 1 ایک ایسا کلسٹر رکھنا ہے جو چوٹی کے بوجھ کو برداشت کرے گا، لیکن باقی وقت بیکار رہے گا۔

حل #2 ایک چھوٹا کلسٹر رکھنا ہے، جس میں آپ کلاسوں سے پہلے اور چوٹی کے بوجھ کے دوران دستی طور پر نوڈس شامل کرتے ہیں۔

حل #3 ایک چھوٹا کلسٹر رکھنا ہے اور ایک آٹو اسکیلر لکھنا ہے جو کلسٹر کے موجودہ بوجھ کی نگرانی کرے گا اور مختلف APIs کا استعمال کرتے ہوئے، کلسٹر سے نوڈس کو شامل اور ہٹائے گا۔

اس پوسٹ میں ہم حل نمبر 3 کے بارے میں بات کریں گے۔ یہ آٹو اسکیلر اندرونی عوامل کے بجائے بیرونی عوامل پر بہت زیادہ انحصار کرتا ہے، اور فراہم کرنے والے اکثر اسے فراہم نہیں کرتے ہیں۔ ہم Mail.ru Cloud Solutions کلاؤڈ انفراسٹرکچر استعمال کرتے ہیں اور MCS API کا استعمال کرتے ہوئے ایک آٹو اسکیلر لکھا ہے۔ اور چونکہ ہم ڈیٹا کے ساتھ کام کرنے کا طریقہ سکھاتے ہیں، اس لیے ہم نے یہ دکھانے کا فیصلہ کیا کہ آپ اپنے مقاصد کے لیے اسی طرح کا آٹو اسکیلر کیسے لکھ سکتے ہیں اور اسے اپنے کلاؤڈ کے ساتھ استعمال کر سکتے ہیں۔

شرائط

سب سے پہلے، آپ کے پاس ہڈوپ کلسٹر ہونا ضروری ہے۔ مثال کے طور پر، ہم HDP تقسیم کا استعمال کرتے ہیں۔

آپ کے نوڈس کو تیزی سے شامل اور ہٹانے کے لیے، آپ کے پاس نوڈس کے درمیان کرداروں کی ایک مخصوص تقسیم ہونی چاہیے۔

  1. ماسٹر نوڈ. ٹھیک ہے، خاص طور پر کسی چیز کی وضاحت کرنے کی ضرورت نہیں ہے: کلسٹر کا مرکزی نوڈ، جس پر، مثال کے طور پر، اسپارک ڈرائیور کو لانچ کیا جاتا ہے، اگر آپ انٹرایکٹو موڈ استعمال کرتے ہیں۔
  2. تاریخ نوڈ. یہ وہ نوڈ ہے جس پر آپ HDFS پر ڈیٹا اسٹور کرتے ہیں اور جہاں حساب کتاب ہوتا ہے۔
  3. کمپیوٹنگ نوڈ۔ یہ ایک نوڈ ہے جہاں آپ HDFS پر کچھ بھی ذخیرہ نہیں کرتے ہیں، لیکن جہاں حساب کتاب ہوتا ہے۔

اہم نکتہ۔ تیسری قسم کے نوڈس کی وجہ سے آٹو اسکیلنگ ہوگی۔ اگر آپ دوسری قسم کے نوڈس لینا اور شامل کرنا شروع کر دیتے ہیں، تو رسپانس کی رفتار بہت کم ہو جائے گی - ڈیکمیشن اور دوبارہ کمٹمنٹ میں آپ کے کلسٹر پر گھنٹے لگیں گے۔ یہ، یقیناً، وہ نہیں ہے جس کی آپ آٹو اسکیلنگ سے توقع کرتے ہیں۔ یعنی ہم پہلی اور دوسری قسم کے نوڈس کو نہیں چھوتے۔ وہ ایک کم از کم قابل عمل کلسٹر کی نمائندگی کریں گے جو پروگرام کی پوری مدت میں موجود رہے گا۔

لہذا، ہمارا آٹو اسکیلر Python 3 میں لکھا گیا ہے، کلسٹر سروسز کو منظم کرنے کے لیے Ambari API کا استعمال کرتا ہے، Mail.ru کلاؤڈ سلوشنز سے API (MCS) مشینیں شروع کرنے اور روکنے کے لیے۔

حل فن تعمیر

  1. ماڈیول autoscaler.py. اس میں تین کلاسز ہیں: 1) امباری کے ساتھ کام کرنے کے فنکشنز، 2) ایم سی ایس کے ساتھ کام کرنے کے فنکشنز، 3) فنکشنز جو براہ راست آٹو اسکیلر کی منطق سے متعلق ہیں۔
  2. اسکرپٹ۔ observer.py. بنیادی طور پر یہ مختلف اصولوں پر مشتمل ہے: کب اور کن لمحات میں آٹو اسکیلر فنکشنز کو کال کرنا ہے۔
  3. کنفیگریشن فائل config.py. اس میں، مثال کے طور پر، آٹو اسکیلنگ کے لیے اجازت دی گئی نوڈس کی فہرست اور دیگر پیرامیٹرز جو متاثر ہوتے ہیں، مثال کے طور پر، ایک نیا نوڈ شامل کیے جانے کے لمحے سے کتنا انتظار کرنا ہے۔ کلاسز کے آغاز کے لیے ٹائم اسٹیمپ بھی موجود ہیں، تاکہ کلاس سے پہلے زیادہ سے زیادہ اجازت یافتہ کلسٹر کنفیگریشن شروع کی جائے۔

آئیے اب پہلی دو فائلوں کے اندر موجود کوڈ کے ٹکڑوں کو دیکھتے ہیں۔

1. Autoscaler.py ماڈیول

عنبری کلاس

کلاس پر مشتمل کوڈ کا ایک ٹکڑا ایسا لگتا ہے۔ Ambari:

class Ambari:
    def __init__(self, ambari_url, cluster_name, headers, auth):
        self.ambari_url = ambari_url
        self.cluster_name = cluster_name
        self.headers = headers
        self.auth = auth

    def stop_all_services(self, hostname):
        url = self.ambari_url + self.cluster_name + '/hosts/' + hostname + '/host_components/'
        url2 = self.ambari_url + self.cluster_name + '/hosts/' + hostname
        req0 = requests.get(url2, headers=self.headers, auth=self.auth)
        services = req0.json()['host_components']
        services_list = list(map(lambda x: x['HostRoles']['component_name'], services))
        data = {
            "RequestInfo": {
                "context":"Stop All Host Components",
                "operation_level": {
                    "level":"HOST",
                    "cluster_name": self.cluster_name,
                    "host_names": hostname
                },
                "query":"HostRoles/component_name.in({0})".format(",".join(services_list))
            },
            "Body": {
                "HostRoles": {
                    "state":"INSTALLED"
                }
            }
        }
        req = requests.put(url, data=json.dumps(data), headers=self.headers, auth=self.auth)
        if req.status_code in [200, 201, 202]:
            message = 'Request accepted'
        else:
            message = req.status_code
        return message

اوپر، مثال کے طور پر، آپ فنکشن کے نفاذ کو دیکھ سکتے ہیں۔ stop_all_services، جو مطلوبہ کلسٹر نوڈ پر تمام خدمات کو روکتا ہے۔

کلاس کے داخلی دروازے پر Ambari آپ پاس:

  • ambari_urlمثال کے طور پر، جیسے 'http://localhost:8080/api/v1/clusters/',
  • cluster_name - امباری میں آپ کے کلسٹر کا نام،
  • headers = {'X-Requested-By': 'ambari'}
  • اور اندر auth امباری کے لیے آپ کا لاگ ان اور پاس ورڈ یہ ہے: auth = ('login', 'password').

فنکشن بذات خود امبری کو REST API کے ذریعے چند کالوں سے زیادہ کچھ نہیں ہے۔ منطقی نقطہ نظر سے، ہم سب سے پہلے نوڈ پر چلنے والی خدمات کی فہرست حاصل کرتے ہیں، اور پھر ایک دیئے گئے کلسٹر پر، دیئے گئے نوڈ پر، خدمات کو فہرست سے ریاست میں منتقل کرنے کے لیے کہتے ہیں۔ INSTALLED. تمام خدمات کو شروع کرنے، ریاست میں نوڈس کی منتقلی کے لیے افعال Maintenance وغیرہ ایک جیسے نظر آتے ہیں - وہ API کے ذریعے صرف چند درخواستیں ہیں۔

کلاس Mcs

کلاس پر مشتمل کوڈ کا ایک ٹکڑا ایسا لگتا ہے۔ Mcs:

class Mcs:
    def __init__(self, id1, id2, password):
        self.id1 = id1
        self.id2 = id2
        self.password = password
        self.mcs_host = 'https://infra.mail.ru:8774/v2.1'

    def vm_turn_on(self, hostname):
        self.token = self.get_mcs_token()
        host = self.hostname_to_vmname(hostname)
        vm_id = self.get_vm_id(host)
        mcs_url1 = self.mcs_host + '/servers/' + self.vm_id + '/action'
        headers = {
            'X-Auth-Token': '{0}'.format(self.token),
            'Content-Type': 'application/json'
        }
        data = {'os-start' : 'null'}
        mcs = requests.post(mcs_url1, data=json.dumps(data), headers=headers)
        return mcs.status_code

کلاس کے داخلی دروازے پر Mcs ہم کلاؤڈ کے اندر پروجیکٹ آئی ڈی اور صارف کی شناخت کے ساتھ ساتھ اس کا پاس ورڈ بھی پاس کرتے ہیں۔ فنکشن میں vm_turn_on ہم مشینوں میں سے ایک کو آن کرنا چاہتے ہیں۔ یہاں منطق کچھ زیادہ ہی پیچیدہ ہے۔ کوڈ کے شروع میں، تین دیگر فنکشنز کو کہا جاتا ہے: 1) ہمیں ایک ٹوکن حاصل کرنے کی ضرورت ہے، 2) ہمیں میزبان نام کو MCS میں مشین کے نام میں تبدیل کرنے کی ضرورت ہے، 3) اس مشین کی آئی ڈی حاصل کریں۔ اگلا، ہم صرف ایک پوسٹ کی درخواست کرتے ہیں اور اس مشین کو لانچ کرتے ہیں۔

ٹوکن حاصل کرنے کا فنکشن ایسا لگتا ہے:

def get_mcs_token(self):
        url = 'https://infra.mail.ru:35357/v3/auth/tokens?nocatalog'
        headers = {'Content-Type': 'application/json'}
        data = {
            'auth': {
                'identity': {
                    'methods': ['password'],
                    'password': {
                        'user': {
                            'id': self.id1,
                            'password': self.password
                        }
                    }
                },
                'scope': {
                    'project': {
                        'id': self.id2
                    }
                }
            }
        }
        params = (('nocatalog', ''),)
        req = requests.post(url, data=json.dumps(data), headers=headers, params=params)
        self.token = req.headers['X-Subject-Token']
        return self.token

آٹو اسکیلر کلاس

یہ کلاس خود آپریٹنگ منطق سے متعلق افعال پر مشتمل ہے۔

اس کلاس کے کوڈ کا ایک ٹکڑا ایسا لگتا ہے:

class Autoscaler:
    def __init__(self, ambari, mcs, scaling_hosts, yarn_ram_per_node, yarn_cpu_per_node):
        self.scaling_hosts = scaling_hosts
        self.ambari = ambari
        self.mcs = mcs
        self.q_ram = deque()
        self.q_cpu = deque()
        self.num = 0
        self.yarn_ram_per_node = yarn_ram_per_node
        self.yarn_cpu_per_node = yarn_cpu_per_node

    def scale_down(self, hostname):
        flag1 = flag2 = flag3 = flag4 = flag5 = False
        if hostname in self.scaling_hosts:
            while True:
                time.sleep(5)
                status1 = self.ambari.decommission_nodemanager(hostname)
                if status1 == 'Request accepted' or status1 == 500:
                    flag1 = True
                    logging.info('Decomission request accepted: {0}'.format(flag1))
                    break
            while True:
                time.sleep(5)
                status3 = self.ambari.check_service(hostname, 'NODEMANAGER')
                if status3 == 'INSTALLED':
                    flag3 = True
                    logging.info('Nodemaneger decommissioned: {0}'.format(flag3))
                    break
            while True:
                time.sleep(5)
                status2 = self.ambari.maintenance_on(hostname)
                if status2 == 'Request accepted' or status2 == 500:
                    flag2 = True
                    logging.info('Maintenance request accepted: {0}'.format(flag2))
                    break
            while True:
                time.sleep(5)
                status4 = self.ambari.check_maintenance(hostname, 'NODEMANAGER')
                if status4 == 'ON' or status4 == 'IMPLIED_FROM_HOST':
                    flag4 = True
                    self.ambari.stop_all_services(hostname)
                    logging.info('Maintenance is on: {0}'.format(flag4))
                    logging.info('Stopping services')
                    break
            time.sleep(90)
            status5 = self.mcs.vm_turn_off(hostname)
            while True:
                time.sleep(5)
                status5 = self.mcs.get_vm_info(hostname)['server']['status']
                if status5 == 'SHUTOFF':
                    flag5 = True
                    logging.info('VM is turned off: {0}'.format(flag5))
                    break
            if flag1 and flag2 and flag3 and flag4 and flag5:
                message = 'Success'
                logging.info('Scale-down finished')
                logging.info('Cooldown period has started. Wait for several minutes')
        return message

ہم داخلہ کے لیے کلاسز قبول کرتے ہیں۔ Ambari и Mcs, نوڈس کی ایک فہرست جو اسکیلنگ کے لیے اجازت دی گئی ہے، نیز نوڈ کنفیگریشن پیرامیٹرز: یارن میں نوڈ کے لیے مختص میموری اور سی پی یو۔ 2 اندرونی پیرامیٹرز q_ram، q_cpu بھی ہیں، جو کہ قطاریں ہیں۔ ان کا استعمال کرتے ہوئے، ہم موجودہ کلسٹر لوڈ کی قدروں کو ذخیرہ کرتے ہیں۔ اگر ہم دیکھتے ہیں کہ پچھلے 5 منٹ کے دوران مسلسل بوجھ میں اضافہ ہوا ہے، تو ہم فیصلہ کرتے ہیں کہ ہمیں کلسٹر میں +1 نوڈ شامل کرنے کی ضرورت ہے۔ کلسٹر انڈر یوٹیلائزیشن سٹیٹ کے لیے بھی یہی سچ ہے۔

مندرجہ بالا کوڈ ایک فنکشن کی ایک مثال ہے جو مشین کو کلسٹر سے ہٹاتا ہے اور اسے کلاؤڈ میں روکتا ہے۔ سب سے پہلے ایک ڈیکمیشن ہے YARN Nodemanager، پھر موڈ آن ہو جاتا ہے۔ Maintenance، پھر ہم مشین پر تمام خدمات بند کر دیتے ہیں اور کلاؤڈ میں ورچوئل مشین کو بند کر دیتے ہیں۔

2. Script observer.py

وہاں سے نمونہ کوڈ:

if scaler.assert_up(config.scale_up_thresholds) == True:
        hostname = cloud.get_vm_to_up(config.scaling_hosts)
        if hostname != None:
            status1 = scaler.scale_up(hostname)
            if status1 == 'Success':
                text = {"text": "{0} has been successfully scaled-up".format(hostname)}
                post = {"text": "{0}".format(text)}
                json_data = json.dumps(post)
                req = requests.post(webhook, data=json_data.encode('ascii'), headers={'Content-Type': 'application/json'})
                time.sleep(config.cooldown_period*60)

اس میں، ہم چیک کرتے ہیں کہ آیا کلسٹر کی صلاحیت کو بڑھانے کے لیے حالات پیدا کیے گئے ہیں اور کیا ریزرو میں کوئی مشینیں موجود ہیں، ان میں سے کسی ایک کا میزبان نام حاصل کریں، اسے کلسٹر میں شامل کریں اور اس کے بارے میں ہماری ٹیم کے Slack پر پیغام شائع کریں۔ جس کے بعد شروع ہوتا ہے۔ cooldown_period، جب ہم کلسٹر سے کچھ شامل یا ہٹاتے نہیں ہیں، لیکن صرف بوجھ کی نگرانی کرتے ہیں۔ اگر یہ مستحکم ہو گیا ہے اور زیادہ سے زیادہ لوڈ اقدار کے کوریڈور کے اندر ہے، تو ہم صرف نگرانی جاری رکھیں گے۔ اگر ایک نوڈ کافی نہیں تھا، تو ہم ایک اور شامل کرتے ہیں۔

ایسے معاملات کے لیے جب ہمارے پاس سبق آگے ہے، ہم پہلے سے ہی یقینی طور پر جانتے ہیں کہ ایک نوڈ کافی نہیں ہوگا، اس لیے ہم فوری طور پر تمام مفت نوڈس کو شروع کرتے ہیں اور سبق کے اختتام تک انہیں فعال رکھتے ہیں۔ یہ سرگرمی ٹائم اسٹیمپ کی فہرست کا استعمال کرتے ہوئے ہوتا ہے۔

حاصل يہ ہوا

آٹو اسکیلر ان صورتوں کے لیے ایک اچھا اور آسان حل ہے جب آپ کو ناہموار کلسٹر لوڈنگ کا تجربہ ہوتا ہے۔ آپ بیک وقت چوٹی کے بوجھ کے لیے مطلوبہ کلسٹر کنفیگریشن حاصل کرتے ہیں اور ساتھ ہی اس کلسٹر کو انڈر لوڈ کے دوران نہ رکھیں، پیسے کی بچت کریں۔ ٹھیک ہے، نیز یہ سب آپ کی شرکت کے بغیر خود بخود ہو جاتا ہے۔ آٹو اسکیلر بذات خود کلسٹر مینیجر API اور کلاؤڈ پرووائیڈر API کو درخواستوں کے ایک سیٹ سے زیادہ کچھ نہیں ہے، جو ایک خاص منطق کے مطابق لکھا گیا ہے۔ جو آپ کو یقینی طور پر یاد رکھنے کی ضرورت ہے وہ ہے نوڈس کی 3 اقسام میں تقسیم، جیسا کہ ہم نے پہلے لکھا تھا۔ اور تم خوش رہو گے۔

ماخذ: www.habr.com

نیا تبصرہ شامل کریں