วิธีตั้งค่าโฮสต์เป้าหมายในไฟล์ Fabric


107

ฉันต้องการใช้ Fabric เพื่อปรับใช้โค้ดเว็บแอปของฉันกับเซิร์ฟเวอร์การพัฒนาการจัดเตรียมและการใช้งานจริง fabfile ของฉัน:

def deploy_2_dev():
  deploy('dev')

def deploy_2_staging():
  deploy('staging')

def deploy_2_prod():
  deploy('prod')

def deploy(server):
  print 'env.hosts:', env.hosts
  env.hosts = [server]
  print 'env.hosts:', env.hosts

ตัวอย่างผลลัพธ์:

host:folder user$ fab deploy_2_dev
env.hosts: []
env.hosts: ['dev']
No hosts found. Please specify (single) host string for connection:

เมื่อฉันสร้างset_hosts()งานตามที่แสดงในเอกสาร Fabric env.hosts จะถูกตั้งค่าอย่างถูกต้อง อย่างไรก็ตามนี่ไม่ใช่ตัวเลือกที่เป็นไปได้และไม่ใช่มัณฑนากร การส่งโฮสต์ในบรรทัดคำสั่งในที่สุดจะส่งผลให้เชลล์สคริปต์บางประเภทเรียก fabfile ฉันต้องการให้เครื่องมือเดียวทำงานได้อย่างถูกต้อง

ในเอกสาร Fabric ระบุว่า 'env.hosts เป็นเพียง Python list object' จากการสังเกตของฉันนี่ไม่เป็นความจริง

ใครช่วยอธิบายได้ไหมว่าเกิดอะไรขึ้นที่นี่? ฉันจะตั้งค่าโฮสต์ให้ใช้งานได้อย่างไร


ฉันมีปัญหาเดียวกันคุณพบวิธีแก้ปัญหานี้หรือไม่?
Martin M.

เพื่อรันงานเดียวกันกับเซิร์ฟเวอร์หลายเครื่องให้ใช้ "fab -H staging-server, production-server deploy" ... เพิ่มเติมในคำตอบของฉันด้านล่าง: stackoverflow.com/a/21458231/26510
Brad Parks


คำตอบนี้ใช้ไม่ได้กับผ้า 2+ หากมีคนคุ้นเคยกับอนุสัญญา Stackoverflow มากขึ้นสามารถแก้ไขคำถามหรือชื่อคำถามเพื่ออ้างถึงแฟบริค 1 ได้อาจเป็นประโยชน์
Jonathan Berger

คำตอบ:


128

ฉันทำได้โดยการประกาศฟังก์ชันจริงสำหรับแต่ละสภาพแวดล้อม ตัวอย่างเช่น:

def test():
    env.user = 'testuser'
    env.hosts = ['test.server.com']

def prod():
    env.user = 'produser'
    env.hosts = ['prod.server.com']

def deploy():
    ...

เมื่อใช้ฟังก์ชันข้างต้นฉันจะพิมพ์สิ่งต่อไปนี้เพื่อปรับใช้กับสภาพแวดล้อมการทดสอบของฉัน:

fab test deploy

... และสิ่งต่อไปนี้เพื่อปรับใช้กับการผลิต:

fab prod deploy

สิ่งที่ดีเกี่ยวกับการทำเช่นนี้คือสามารถใช้ฟังก์ชันtestand prodก่อนฟังก์ชัน fab ใด ๆไม่ใช่แค่ปรับใช้ มันมีประโยชน์อย่างเหลือเชื่อ


10
เนื่องจากข้อผิดพลาดใน fabric ( code.fabfile.org/issues/show/138#change-1497 ) จึงเป็นการดีกว่าที่จะรวมผู้ใช้ไว้ในสตริงโฮสต์ (เช่น produser@prod.server.com) แทนการตั้งค่า env.user
Mikhail Korobov

1
ฉันมีปัญหาเดียวกันและดูเหมือนว่าจะเป็นทางออกที่ดีที่สุด ฉันกำหนดโฮสต์ผู้ใช้และการตั้งค่าอื่น ๆ ในไฟล์ YAML ที่โหลดโดยฟังก์ชัน dev () และ prod () (เพื่อให้ฉันสามารถใช้สคริปต์ Fabric เดียวกันซ้ำสำหรับโปรเจ็กต์ที่คล้ายกันได้)
Christian Davén

@MikhailKorobov: เมื่อฉันไปตามลิงก์ของคุณฉันเห็น " ยินดีต้อนรับสู่ nginx! " คำขอทั้งหมดไปยังcode.fabfile.orgโดเมนมีการตอบสนองเช่นนั้น
Tadeck

ใช่ดูเหมือนว่าข้อบกพร่องทั้งหมดจะถูกย้ายไปที่ github
Mikhail Korobov

2
น่าเสียดายที่ดูเหมือนว่าจะใช้ไม่ได้อีกต่อไป - fabric จะไม่รันงานโดยไม่ได้กำหนด env.hosts ไว้แล้วและจะไม่เรียกใช้ฟังก์ชันในfab A B Cรูปแบบหากไม่มีการกำหนดเป็นงาน
DNelson

77

ใช้roledefs

from fabric.api import env, run

env.roledefs = {
    'test': ['localhost'],
    'dev': ['user@dev.example.com'],
    'staging': ['user@staging.example.com'],
    'production': ['user@production.example.com']
} 

def deploy():
    run('echo test')

เลือกบทบาทด้วย -R:

$ fab -R test deploy
[localhost] Executing task 'deploy'
...

7
หรือถ้างานรันด้วยบทบาทเดียวกันเสมอคุณสามารถใช้ @roles () มัณฑนากรกับงาน
ทอม

2
ดูเหมือนว่า roledefs เป็นทางออกที่ดีกว่าการกำหนดเป็นงานแยกต่างหาก
Ehtesh Choudhury

มีใครทราบบ้างไหมว่าฉันจะใส่รหัสผ่านสำหรับชื่อผู้ใช้ที่ระบุไว้ใน a roledef? 'password': 'some_password'ดูเหมือนว่ารายการพจนานุกรมเพิ่มเติมจะถูกละเว้นและนำไปสู่พร้อมต์ที่รันไทม์
Dirk

@Dirk คุณสามารถใช้ env.passwords ซึ่งเป็นพจนานุกรมที่มี user + host + port เป็นคีย์และรหัสผ่านเป็นค่า เช่น env.passwords = {'user @ host: 22': 'password'}
Jonathan

49

นี่คือคำตอบของ serverhorrorเวอร์ชันที่ง่ายกว่า:

from fabric.api import settings

def mystuff():
    with settings(host_string='192.0.2.78'):
        run("hostname -f")

2
ตามเอกสารตัวจัดการบริบทการตั้งค่ามีไว้สำหรับการลบล้างenvตัวแปรไม่ใช่สำหรับการตั้งค่าในตอนแรก ฉันคิดว่าการใช้roledefsตามที่ thomie แนะนำนั้นเหมาะสมกว่าสำหรับการกำหนดโฮสต์เช่น stage, dev และ test
Tony

21

ติดอยู่กับตัวเอง แต่สุดท้ายก็คิดออก คุณไม่สามารถตั้งค่าการกำหนดค่า env.hosts จากภายในได้งานได้ แต่ละงานจะถูกดำเนินการ N ครั้งหนึ่งครั้งสำหรับแต่ละโฮสต์ที่ระบุดังนั้นการตั้งค่าจึงอยู่นอกขอบเขตงานโดยพื้นฐาน

ดูรหัสของคุณด้านบนคุณสามารถทำได้ง่ายๆ:

@hosts('dev')
def deploy_dev():
    deploy()

@hosts('staging')
def deploy_staging():
    deploy()

def deploy():
    # do stuff...

ซึ่งดูเหมือนว่ามันจะทำในสิ่งที่คุณตั้งใจ

หรือคุณสามารถเขียนโค้ดที่กำหนดเองบางส่วนในขอบเขตส่วนกลางที่แยกวิเคราะห์อาร์กิวเมนต์ด้วยตนเองและตั้งค่า env.hosts ก่อนกำหนดฟังก์ชันงานของคุณ ด้วยเหตุผลบางประการนั่นคือวิธีที่ฉันตั้งค่าของฉัน


พบวิธี: from fabric.api import env; env.host_string = "dev"
โรมัน

18

เนื่องจาก fab 1.5 นี่เป็นวิธีการตั้งค่าโฮสต์แบบไดนามิก

http://docs.fabfile.org/en/1.7/usage/execution.html#dynamic-hosts

อ้างจากเอกสารด้านล่าง

ใช้ execute กับรายการโฮสต์ที่ตั้งค่าแบบไดนามิก

กรณีการใช้งานระดับกลางถึงขั้นสูงทั่วไปสำหรับ Fabric คือการกำหนดพารามิเตอร์การค้นหารายการโฮสต์เป้าหมายที่รันไทม์ (เมื่อใช้ Roles ไม่เพียงพอ) การดำเนินการสามารถทำให้สิ่งนี้ง่ายมากเช่น:

from fabric.api import run, execute, task

# For example, code talking to an HTTP API, or a database, or ...
from mylib import external_datastore

# This is the actual algorithm involved. It does not care about host
# lists at all.
def do_work():
    run("something interesting on a host")

# This is the user-facing task invoked on the command line.
@task
def deploy(lookup_param):
    # This is the magic you don't get with @hosts or @roles.
    # Even lazy-loading roles require you to declare available roles
    # beforehand. Here, the sky is the limit.
    host_list = external_datastore.query(lookup_param)
    # Put this dynamically generated host list together with the work to be
    # done.
    execute(do_work, hosts=host_list)

3
+1. คำตอบที่ดีมากมายที่ด้านล่างของหน้านี้
Matt Montag

10

ขัดกับคำตอบอื่น ๆ บางอย่างก็เป็นไปได้ที่จะปรับเปลี่ยนenvตัวแปรสภาพแวดล้อมภายในงาน อย่างไรก็ตามสิ่งนี้envจะใช้สำหรับงานต่อ ๆ ไปที่ดำเนินการโดยใช้fabric.tasks.executeฟังก์ชัน

from fabric.api import task, roles, run, env
from fabric.tasks import execute

# Not a task, plain old Python to dynamically retrieve list of hosts
def get_stressors():
    hosts = []
    # logic ...
    return hosts

@task
def stress_test():
    # 1) Dynamically generate hosts/roles
    stressors = get_stressors()
    env.roledefs['stressors'] = map(lambda x: x.public_ip, stressors)

    # 2) Wrap sub-tasks you want to execute on new env in execute(...)
    execute(stress)

    # 3) Note that sub-tasks not nested in execute(...) will use original env
    clean_up()

@roles('stressors')
def stress():
    # this function will see any changes to env, as it was wrapped in execute(..)
    run('echo "Running stress test..."')
    # ...

@task
def clean_up():
    # this task will NOT see any dynamic changes to env

โดยไม่ต้องรวมงานย่อยไว้การตั้งค่าexecute(...)ระดับโมดูลของคุณenvหรืออะไรก็ตามที่ส่งผ่านจากfabCLI จะถูกใช้


นี่คือคำตอบที่ดีที่สุดหากคุณต้องการตั้งค่า env.hosts แบบไดนามิก
JahMyst

9

คุณต้องกำหนดhost_stringตัวอย่างคือ:

from fabric.context_managers import settings as _settings

def _get_hardware_node(virtualized):
    return "localhost"

def mystuff(virtualized):
    real_host = _get_hardware_node(virtualized)
    with _settings(
        host_string=real_host):
        run("echo I run on the host %s :: `hostname -f`" % (real_host, ))

หวาน. ฉันได้โพสต์โค้ดเวอร์ชันที่ง่ายกว่าในคำตอบอื่นที่นี่
tobych

9

เพื่ออธิบายว่าเหตุใดจึงเป็นปัญหา คำสั่งfabกำลังใช้โครงสร้างไลบรารีเพื่อรันงานในรายการโฮสต์ หากคุณลองเปลี่ยนรายการโฮสต์ภายในงานแสดงว่าคุณกำลังพยายามเปลี่ยนรายการในขณะที่ทำซ้ำ หรือในกรณีที่คุณไม่ได้กำหนดโฮสต์ไว้ให้วนรอบรายการว่างโดยที่โค้ดที่คุณตั้งค่าให้รายการวนซ้ำจะไม่ถูกเรียกใช้

การใช้ env.host_string เป็นการแก้ปัญหาสำหรับพฤติกรรมนี้เฉพาะในกรณีที่ระบุโดยตรงกับฟังก์ชันที่โฮสต์จะเชื่อมต่อ สิ่งนี้ทำให้เกิดปัญหาบางอย่างในการที่คุณจะสร้างลูปการดำเนินการใหม่หากคุณต้องการให้โฮสต์จำนวนมากดำเนินการ

วิธีที่ง่ายที่สุดที่ผู้ใช้สามารถตั้งค่าโฮสต์ในขณะรันไทม์คือการทำให้ env populatiing เป็นงานที่แตกต่างกันซึ่งจะตั้งค่าสตริงโฮสต์ผู้ใช้ ฯลฯ ทั้งหมดจากนั้นจึงรันงานปรับใช้ ดูเหมือนว่า:

fab production deploy

หรือ

fab staging deploy

ในกรณีที่การจัดเตรียมและการผลิตเป็นเหมือนงานที่คุณมอบให้ แต่จะไม่เรียกงานถัดไปด้วยตนเอง เหตุผลที่มันต้องทำงานแบบนี้คืองานต้องเสร็จและแยกออกจากลูป (ของโฮสต์ในกรณี env ไม่มี แต่มันวนซ้ำอยู่ที่จุดนั้น) แล้ววนซ้ำ โฮสต์ (ตอนนี้กำหนดโดยภารกิจก่อนหน้านี้) อีกครั้ง


3

คุณต้องแก้ไข env.hosts ในระดับโมดูลไม่ใช่ภายในฟังก์ชันงาน ฉันทำผิดเหมือนกัน

from fabric.api import *

def _get_hosts():
    hosts = []
    ... populate 'hosts' list ...
    return hosts

env.hosts = _get_hosts()

def your_task():
    ... your task ...

3

มันง่ายมาก เพียงเตรียมใช้งานตัวแปร env.host_string จากนั้นคำสั่งต่อไปนี้ทั้งหมดจะถูกเรียกใช้บนโฮสต์

from fabric.api import env, run

env.host_string = 'user@exmaple.com'

def foo:
    run("hostname -f")

3

ฉันใหม่มากสำหรับ fabric แต่ต้องการให้ fabric รันคำสั่งเดียวกันบนหลาย ๆ โฮสต์ (เช่นเพื่อปรับใช้กับเซิร์ฟเวอร์หลายเครื่องในคำสั่งเดียว) คุณสามารถเรียกใช้:

fab -H staging-server,production-server deploy 

โดยที่staging-serverและproduction-serverคือ 2 เซิร์ฟเวอร์ที่คุณต้องการรันการดำเนินการปรับใช้ นี่คือ fabfile.py ง่ายๆที่จะแสดงชื่อระบบปฏิบัติการ โปรดสังเกตว่า fabfile.py ควรอยู่ในไดเร็กทอรีเดียวกับที่คุณรันคำสั่ง fab

from fabric.api import *

def deploy():
    run('uname -s')

สิ่งนี้ใช้ได้กับผ้า 1.8.1 เป็นอย่างน้อย


3

ดังนั้นในการตั้งค่าโฮสต์และให้คำสั่งทำงานในโฮสต์ทั้งหมดคุณต้องเริ่มต้นด้วย:

def PROD():
    env.hosts = ['10.0.0.1', '10.0.0.2']

def deploy(version='0.0'):
    sudo('deploy %s' % version)

เมื่อกำหนดแล้วให้รันคำสั่งในบรรทัดคำสั่ง:

fab PROD deploy:1.5

สิ่งที่จะรันงานการปรับใช้ในเซิร์ฟเวอร์ทั้งหมดที่แสดงรายการในฟังก์ชัน PROD เนื่องจากตั้งค่า env.hosts ก่อนที่จะรันงาน


สมมติว่าการปรับใช้บนโฮสต์แรกทำงานได้ แต่โฮสต์ที่สองล้มเหลวฉันจะทำอีกครั้งในโฮสต์ที่สองได้อย่างไร
เลขที่

2

คุณสามารถกำหนดenv.hoststringก่อนที่จะดำเนินการงานย่อย กำหนดให้กับตัวแปรส่วนกลางนี้แบบวนซ้ำหากคุณต้องการวนซ้ำบนโฮสต์หลาย ๆ โฮสต์

น่าเสียดายสำหรับคุณและฉันผ้าไม่ได้ออกแบบมาสำหรับกรณีการใช้งานนี้ ตรวจสอบmainฟังก์ชันที่http://github.com/bitprophet/fabric/blob/master/fabric/main.pyเพื่อดูวิธีการทำงาน


2

นี่คือรูปแบบ "summersault" อีกรูปแบบหนึ่งที่ทำให้สามารถfab my_env_1 my_commandใช้งานได้:

ด้วยรูปแบบนี้เราต้องกำหนดสภาพแวดล้อมเพียงครั้งเดียวโดยใช้พจนานุกรม สร้างฟังก์ชั่นตั้งอยู่บนพื้นฐานของenv_factory keynames ENVSฉันใส่ENVSไดเร็กทอรีและไฟล์ของตัวเองsecrets.config.pyเพื่อแยกการกำหนดค่าออกจากรหัสผ้า

ข้อเสียเปรียบก็คือว่าในขณะที่เขียนเพิ่ม@taskมัณฑนากรจะทำลายมัน

หมายเหตุ: เราใช้def func(k=k):แทนdef func():ในโรงงานเพราะผูกพันปลาย เราได้รับโมดูลการทำงานพร้อมโซลูชันนี้และแก้ไขเพื่อกำหนดฟังก์ชัน

secret.config.py

ENVS = {
    'my_env_1': {
        'HOSTS': [
            'host_1',
            'host_2',
        ],
        'MY_OTHER_SETTING': 'value_1',
    },
    'my_env_2': {
        'HOSTS': ['host_3'],
        'MY_OTHER_SETTING': 'value_2'
    }
}

fabfile.py

import sys
from fabric.api import env
from secrets import config


def _set_env(env_name):
    # can easily customize for various use cases
    selected_config = config.ENVS[env_name]
    for k, v in selected_config.items():
        setattr(env, k, v)


def _env_factory(env_dict):
    for k in env_dict:
        def func(k=k):
            _set_env(k)
        setattr(sys.modules[__name__], k, func)


_env_factory(config.ENVS)

def my_command():
    # do work

0

ปัจจุบันการใช้บทบาทถือเป็นวิธีที่ "เหมาะสม" และ "ถูกต้อง" ในการดำเนินการนี้และเป็นสิ่งที่คุณ "ควร" ทำ

ที่กล่าวว่าหากคุณเป็นเหมือนสิ่งที่คุณ "ต้องการ" หรือ "ปรารถนา" ส่วนใหญ่ก็คือความสามารถในการ "บิดซิสเตอร์" หรือเปลี่ยนระบบเป้าหมายได้ทันที

ดังนั้นเพื่อความบันเทิงเท่านั้น (!) ตัวอย่างต่อไปนี้แสดงให้เห็นถึงสิ่งที่หลายคนอาจคิดว่ามีความเสี่ยง แต่ก็น่าพอใจอย่างยิ่งการซ้อมรบที่ดำเนินไปในลักษณะนี้:

env.remote_hosts       = env.hosts = ['10.0.1.6']
env.remote_user        = env.user = 'bob'
env.remote_password    = env.password = 'password1'
env.remote_host_string = env.host_string

env.local_hosts        = ['127.0.0.1']
env.local_user         = 'mark'
env.local_password     = 'password2'

def perform_sumersault():
    env_local_host_string = env.host_string = env.local_user + '@' + env.local_hosts[0]
    env.password = env.local_password
    run("hostname -f")
    env.host_string = env.remote_host_string
    env.remote_password = env.password
    run("hostname -f")

จากนั้นวิ่ง:

fab perform_sumersault
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.