Cara mengatur host target di file Fabric


107

Saya ingin menggunakan Fabric untuk menerapkan kode aplikasi web saya ke server pengembangan, pementasan, dan produksi. Fabfile saya:

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

Output sampel:

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

Saat saya membuat set_hosts()tugas seperti yang ditunjukkan di dokumen Fabric , env.hosts disetel dengan benar. Namun, ini bukan pilihan yang tepat, begitu pula dekorator. Meneruskan host pada baris perintah pada akhirnya akan menghasilkan semacam skrip shell yang memanggil fabfile, saya lebih suka memiliki satu alat yang melakukan pekerjaan dengan benar.

Dikatakan dalam dokumen Fabric bahwa 'env.hosts hanyalah objek daftar Python'. Dari pengamatan saya, ini tidak benar.

Adakah yang bisa menjelaskan apa yang terjadi di sini? Bagaimana cara mengatur host untuk menerapkan?


Saya memiliki masalah yang sama, apakah Anda menemukan solusi untuk ini?
Martin M.

untuk menjalankan tugas yang sama terhadap beberapa server, gunakan "fab -H staging-server, production-server deploy" ... selengkapnya dalam jawaban saya di bawah ini: stackoverflow.com/a/21458231/26510
Brad Parks


Jawaban ini tidak berlaku untuk kain 2+. Jika seseorang yang lebih paham dengan konvensi Stackoverflow dapat mengedit pertanyaan atau judul pertanyaan untuk merujuk ke fabric 1, mungkin akan membantu.
Jonathan Berger

Jawaban:


128

Saya melakukan ini dengan mendeklarasikan fungsi aktual untuk setiap lingkungan. Sebagai contoh:

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

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

def deploy():
    ...

Menggunakan fungsi di atas, saya akan mengetik yang berikut ini untuk diterapkan ke lingkungan pengujian saya:

fab test deploy

... dan berikut ini untuk diterapkan ke produksi:

fab prod deploy

Hal yang menyenangkan tentang melakukannya dengan cara ini adalah bahwa fungsi testdan proddapat digunakan sebelum fungsi hebat apa pun , tidak hanya menerapkan. Ini sangat berguna.


10
Karena bug di fabric ( code.fabfile.org/issues/show/138#change-1497 ), lebih baik menyertakan pengguna dalam string host (seperti produser@prod.server.com) daripada menyetel env.user.
Mikhail Korobov

1
Saya memiliki masalah yang sama, dan ini sepertinya solusi terbaik. Saya mendefinisikan host, pengguna, dan banyak pengaturan lain dalam file YAML yang dimuat oleh fungsi dev () dan prod (). (Sehingga saya dapat menggunakan kembali skrip Fabric yang sama untuk proyek serupa.)
Christian Davén

@MikhailKorobov: Ketika saya mengikuti tautan Anda, saya melihat " Selamat datang di nginx! ". Semua permintaan ke code.fabfile.orgdomain memiliki tanggapan seperti itu.
Tadeck

Ya, sepertinya semua bug telah dipindahkan ke github.
Mikhail Korobov

2
Sayangnya, sepertinya ini tidak lagi berfungsi - fabric tidak akan menjalankan tugas tanpa env.hosts yang sudah ditentukan, dan tidak akan menjalankan fungsi dalam fab A B Cgaya tanpa didefinisikan sebagai tugas.
DNelson

77

Gunakan 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')

Pilih peran dengan -R:

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

7
Atau jika tugas selalu dijalankan pada peran yang sama, Anda dapat menggunakan dekorator @roles () pada tugas tersebut.
Tom

2
Kedengarannya seperti roledefs adalah solusi yang lebih baik daripada mendefinisikannya dalam tugas terpisah.
Ehtesh Choudhury

Adakah yang tahu bagaimana saya bisa memasukkan kata sandi untuk nama pengguna yang disediakan di roledef? Entri kamus lebih lanjut 'password': 'some_password'tampaknya diabaikan dan mengarah ke prompt pada waktu proses.
Dirk

@ Dirk Anda dapat menggunakan env.passwords yang merupakan kamus yang berisi pengguna + host + port sebagai kunci dan kata sandi sebagai nilai. Misalnya env.passwords = {'user @ host: 22': 'password'}
Jonathan

49

Berikut adalah versi sederhana dari jawaban serverhorror :

from fabric.api import settings

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

2
Sesuai dengan dokumen , pengelola konteks pengaturan adalah untuk mengganti envvariabel, bukan untuk mengaturnya pada awalnya. Saya pikir menggunakan roledefs , seperti yang disarankan thomie, lebih tepat untuk mendefinisikan host seperti stage, dev dan test.
Tony

21

Terjebak pada ini sendiri, tetapi akhirnya menemukan jawabannya. Anda tidak dapat mengatur konfigurasi env.hosts dari dalam tugas. Setiap tugas dijalankan N kali, satu kali untuk setiap Host yang ditentukan, sehingga pengaturan pada dasarnya berada di luar cakupan tugas.

Melihat kode Anda di atas, Anda cukup melakukan ini:

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

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

def deploy():
    # do stuff...

Yang sepertinya akan melakukan apa yang Anda inginkan.

Atau Anda dapat menulis beberapa kode kustom dalam cakupan global yang mengurai argumen secara manual, dan menyetel env.hosts sebelum fungsi tugas Anda ditentukan. Untuk beberapa alasan, itulah cara saya mengatur milik saya.


Menemukan cara from fabric.api import env:; env.host_string = "dev"
Roman

18

Sejak fab 1.5 ini adalah cara terdokumentasi untuk mengatur host secara dinamis.

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

Kutipan dari dokumen di bawah ini.

Menggunakan eksekusi dengan daftar host yang ditetapkan secara dinamis

Kasus penggunaan menengah-hingga-lanjutan yang umum untuk Fabric adalah untuk membuat parameter pencarian daftar host target seseorang saat runtime (ketika penggunaan Peran tidak mencukupi). mengeksekusi dapat membuat ini sangat sederhana, seperti:

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. Banyak jawaban yang sangat bagus di bagian bawah halaman di sini.
Matt Montag

10

Bertentangan dengan beberapa jawaban lain, adalah mungkin untuk mengubah envvariabel lingkungan dalam tugas. Namun, ini envhanya akan digunakan untuk tugas berikutnya yang dijalankan dengan menggunakan fabric.tasks.executefungsi tersebut.

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

Tanpa membungkus sub-tugas execute(...), envpengaturan tingkat modul Anda atau apa pun yang diteruskan dari fabCLI akan digunakan.


Ini adalah jawaban terbaik jika Anda ingin menyetel env.hosts secara dinamis.
JahMyst

9

Anda perlu host_stringmemberi contoh seperti ini:

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, ))

Manis. Saya telah memposting versi kode yang lebih sederhana dalam jawaban lain di sini.
tobych

9

Untuk menjelaskan mengapa itu menjadi masalah. Hebat perintah memanfaatkan fabric library untuk menjalankan tugas pada daftar host. Jika Anda mencoba dan mengubah daftar host di dalam tugas, pada dasarnya Anda mencoba mengubah daftar sambil mengulanginya. Atau dalam kasus di mana Anda tidak memiliki host yang ditentukan, ulangi daftar kosong di mana kode tempat Anda mengatur daftar untuk mengulang tidak pernah dijalankan.

Penggunaan env.host_string adalah solusi untuk perilaku ini hanya karena ia menentukan secara langsung fungsi yang akan dihubungkan dengan host. Ini menyebabkan beberapa masalah di mana Anda akan membuat ulang loop eksekusi jika Anda ingin memiliki sejumlah host untuk dieksekusi.

Cara paling sederhana orang membuat kemampuan untuk mengatur host pada waktu proses, adalah dengan menjaga env populatiing sebagai tugas yang berbeda, yang mengatur semua string host, pengguna, dll. Kemudian mereka menjalankan tugas penerapan. Ini terlihat seperti ini:

fab production deploy

atau

fab staging deploy

Dimana pementasan dan produksi seperti tugas yang telah Anda berikan, tetapi mereka tidak memanggil sendiri tugas berikutnya. Alasan itu harus bekerja seperti ini, adalah bahwa tugas harus selesai, dan keluar dari loop (dari host, dalam kasus env Tidak ada, tetapi ini adalah loop dari satu pada saat itu), dan kemudian loop berakhir host (sekarang ditentukan oleh tugas sebelumnya) lagi.


3

Anda perlu memodifikasi env.hosts di tingkat modul, bukan dalam fungsi tugas. Saya melakukan kesalahan yang sama.

from fabric.api import *

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

env.hosts = _get_hosts()

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

3

Sangat sederhana. Cukup inisialisasi variabel env.host_string dan semua perintah berikut akan dijalankan di host ini.

from fabric.api import env, run

env.host_string = 'user@exmaple.com'

def foo:
    run("hostname -f")

3

Saya benar-benar baru mengenal fabric, tetapi agar fabric menjalankan perintah yang sama pada banyak host (misalnya untuk menerapkan ke banyak server, dalam satu perintah) Anda dapat menjalankan:

fab -H staging-server,production-server deploy 

di mana staging-server dan production-server adalah 2 server yang Anda inginkan untuk menjalankan tindakan penerapan. Berikut ini fabfile.py sederhana yang akan menampilkan nama OS. Perhatikan bahwa fabfile.py harus berada di direktori yang sama dengan tempat Anda menjalankan perintah fab.

from fabric.api import *

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

Ini bekerja setidaknya dengan kain 1.8.1.


3

Jadi, untuk mengatur host, dan menjalankan perintah di semua host, Anda harus mulai dengan:

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

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

Setelah itu ditentukan, lalu jalankan perintah pada baris perintah:

fab PROD deploy:1.5

Apa yang akan menjalankan tugas penerapan di semua server yang terdaftar di fungsi PROD, karena menetapkan env.hosts sebelum menjalankan tugas.


Misalkan penerapan pada host pertama berhasil tetapi yang kedua gagal, bagaimana cara melakukannya lagi hanya pada yang kedua?
no


2

Berikut pola "summersault" lain yang memungkinkan fab my_env_1 my_commandpenggunaan:

Dengan pola ini, kita hanya perlu mendefinisikan lingkungan satu kali menggunakan kamus. env_factorymembuat fungsi berdasarkan nama kunci dari ENVS. Saya memasukkan ENVSdirektori dan file sendiri secrets.config.pyuntuk memisahkan konfigurasi dari kode fabric.

Kekurangannya adalah, seperti yang tertulis, menambahkan @taskdekorator akan merusaknya .

Catatan: Kami menggunakan def func(k=k):bukan def func():di pabrik karena penjilidan terlambat . Kami mendapatkan modul yang sedang berjalan dengan solusi ini dan menambalnya untuk menentukan fungsi.

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

Menggunakan peran saat ini dianggap sebagai cara yang "tepat" dan "benar" untuk melakukan ini dan itulah yang "harus" Anda lakukan.

Yang mengatakan, jika Anda seperti kebanyakan dari apa yang Anda "inginkan" atau "inginkan" adalah kemampuan untuk melakukan "syster bengkok" atau mengganti sistem target dengan cepat.

Jadi untuk tujuan hiburan saja (!) Contoh berikut mengilustrasikan apa yang mungkin dianggap banyak orang sebagai manuver berisiko, namun entah bagaimana benar-benar memuaskan, yang berlangsung seperti ini:

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")

Lalu lari:

fab perform_sumersault
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.