ในฐานะนักพัฒนาซอฟต์แวร์หลักใน Cloudify.co ฉันมักจะทำงานบนโฮสต์ระยะไกลที่ใช้งานบนบริการคลาวด์ เช่น AWS หรือ Openstack งานประเภทนี้ทำให้คุณเกิดคำถาม: ฉันจะรันคำสั่งเดียวกัน (สคริปต์) กับคำสั่งทั้งหมดโดยอัตโนมัติได้อย่างไร คำตอบมักจะใช้ Ansible หรือ Fabric ในบทความนี้ ฉันตัดสินใจที่จะมุ่งเน้นไปที่โซลูชัน Fabric

ผ้าคืออะไร?

Fabric เป็นไลบรารี Python ระดับสูง (2.7, 3.4+) ที่ออกแบบมาเพื่อรันคำสั่งเชลล์จากระยะไกลผ่าน SSH โดยให้วัตถุ Python ที่เป็นประโยชน์เป็นการตอบแทน ("เว็บไซต์ Fabric")

มาเขียนโค้ดกับมันกันดีกว่า (ฉันใช้ Python 3.6 และ Fabric 2.5.0)

การเริ่มต้นใช้งาน — การสร้างการเชื่อมต่อ:

เนื่องจากเราใช้ python วิธีที่ดีที่สุดในการแสดงความสามารถของ Fabric คือการใช้ object เราจะเรียกมันว่า 'Host'

class Host(object):
    def __init__(self,
                 host_ip,
                 username,
                 key_file_path):
        self.host_ip = host_ip
        self.username = username
        self.key_file_path = key_file_path

    def _get_connection(self):
        connect_kwargs = {'key_filename': self.key_file_path}
        return Connection(host=self.host_ip, user=self.username, 
                          port=22,
                          connect_kwargs=connect_kwargs)

ในโค้ดด้านบน `Host` ถูกใช้เป็น "โฮสต์ระยะไกล" และใช้ฟังก์ชัน `_get_connection` เพื่อสร้างการเชื่อมต่อ SSH (เพื่ออ่านเพิ่มเติมเกี่ยวกับ `การเชื่อมต่อ`) เราจะใช้การเชื่อมต่อเป็นตัวจัดการบริบทเนื่องจากมีวงจรชีวิตพื้นฐาน “สร้าง เชื่อมต่อ/เปิด ทำงาน ยกเลิกการเชื่อมต่อ/ปิด” (จากเอกสาร)

คำสั่งที่กำลังรันอยู่:

ตอนนี้เรามีการเชื่อมต่อแล้ว เราก็สามารถรันคำสั่งบนรีโมตโฮสต์ของเราได้ ฟังก์ชั่นต่อไปนี้จะถูกเพิ่มเข้าไปในวัตถุ 'โฮสต์':

def run_command(self, command):
    try:
        with self._get_connection() as connection:
            print('Running `{0}` on {1}'.format(command,   
                   self.host_ip))
            result = connection.run(command, warn=True, 
                                    hide='stderr')
    except (socket_error, AuthenticationException) as exc:
        self._raise_authentication_err(exc)

    if result.failed:
        raise ExampleException(
            'The command `{0}` on host {1} failed with the error: '
            '{2}'.format(command, self.host_ip, str(result.stderr)))

def put_file(self, local_path, remote_path):
    try:
        with self._get_connection() as connection:
            print('Copying {0} to {1} on host {2}'.format(
                local_path, remote_path, self.host_ip))
            connection.put(local_path, remote_path)
    except (socket_error, AuthenticationException) as exc:
        self._raise_authentication_err(exc)

def _raise_authentication_err(self, exc):
    raise ExampleException(
        "SSH: could not connect to {host} "
        "(username: {user}, key: {key}): {exc}".format(
            host=self.host_ip, user=self.username,
            key=self.key_file_path, exc=exc))

ในโค้ดข้างต้น ฟังก์ชันต่างๆ:

`run_command` ใช้เพื่อรันคำสั่งบนโฮสต์ระยะไกล คุณสามารถส่งผ่านฟังก์ชัน `Connect.run` พารามิเตอร์ต่างๆ ได้มากมาย ("ตามที่อธิบายไว้ที่นี่") แต่สิ่งที่ฉันต้องการสำหรับกลไก "การตรวจจับข้อผิดพลาด" คือ:

  • `warn=True`: อย่าสร้างข้อยกเว้นเมื่อคำสั่งที่ดำเนินการออกโดยมีสถานะไม่เป็นศูนย์ (หมายถึงความล้มเหลว)
  • `hide='stderr'': อย่าคัดลอกกระบวนการย่อย' stderr ไปยังเทอร์มินัลการควบคุม

พฤติกรรมเริ่มต้นของ `Connection.run` คือการคัดลอก stdout และ stderr ของกระบวนการย่อยไปยังเทอร์มินัลการควบคุม

`put_file` ใช้เพื่อคัดลอกไฟล์จากโลคัลโฮสต์ไปยังรีโมตโฮสต์ (เพื่ออ่านเพิ่มเติมเกี่ยวกับ `Connection.put`)

สิ่งสำคัญที่ควรทราบ คลาสการเชื่อมต่อคือ "ขี้เกียจ" ซึ่งหมายความว่าเมื่อเราเริ่มต้นคลาสนั้น จะไม่พยายามเชื่อมต่อกับโฮสต์ ดังนั้นเราจึงใส่คำสั่งของเราไว้ในบล็อกลองยกเว้นที่จะตรวจจับข้อผิดพลาดในการเชื่อมต่อ

สรุป:

การตัดสินใจเลือกระหว่าง Ansible และ Fabric มักจะขึ้นอยู่กับสิ่งที่คุณต้องการรันบนโฮสต์ระยะไกล หากเป็นสคริปต์ธรรมดา Fabric ก็ควรจะดีเพียงพอ ไม่เช่นนั้น Ansible อาจเป็นตัวเลือกที่ดีกว่า ไม่ว่าจะด้วยวิธีใด Cloudify Manager ให้คุณเลือกโซลูชันที่เหมาะกับคุณโดยใช้ปลั๊กอิน Ansible และ Fabric มีความสุข! :)

ภาคผนวก:

นี่คือตัวอย่างโค้ดแบบเต็มที่คุณสามารถใช้ได้ (อย่าลืมแทนที่พารามิเตอร์ `Host` ให้ตรงกับพารามิเตอร์ของคุณเอง):