program story

패브릭 파일에서 대상 호스트를 설정하는 방법

inputbox 2020. 8. 6. 08:18
반응형

패브릭 파일에서 대상 호스트를 설정하는 방법


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:

Fabric docs에set_hosts() 표시된 것처럼 작업을 만들면 env.hosts가 올바르게 설정됩니다. 그러나 이것은 실행 가능한 옵션이 아니며 데코레이터도 아닙니다. 커맨드 라인에서 호스트를 전달하면 궁극적으로 fabfile을 호출하는 일종의 쉘 스크립트가 생성되므로 하나의 도구로 작업을 올바르게 수행하는 것이 좋습니다.

Fabric 문서에서 'env.hosts는 단순히 Python 목록 객체입니다.'라고 말합니다. 내 관찰에 따르면 이것은 사실이 아닙니다.

아무도 여기서 무슨 일이 일어나고 있는지 설명 할 수 있습니까? 배포 할 호스트를 어떻게 설정합니까?


각 환경에 대한 실제 기능을 선언하여이 작업을 수행합니다. 예를 들면 다음과 같습니다.

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 기능 보다 먼저 사용할 수 있다는 것 입니다. 매우 유용합니다.


사용 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'
...

다음은 간단한 버전의 서버 호러입니다 .

from fabric.api import settings

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

이 자신에 붙어 있지만 마침내 알아 냈습니다. 작업 에서 간단히 env.hosts 구성을 설정할 수 없습니다 . 각 작업은 지정된 각 호스트에 대해 한 번씩 N 번 실행되므로 설정은 기본적으로 작업 범위를 벗어납니다.

위의 코드를 보면 간단히 다음과 같이 할 수 있습니다.

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

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

def deploy():
    # do stuff...

당신이 원하는 것을 할 것 같습니다.

또는 전역 범위에서 인수를 수동으로 구문 분석하고 태스크 함수가 정의되기 전에 env.hosts를 설정하는 사용자 정의 코드를 작성할 수 있습니다. 몇 가지 이유로, 실제로 내가 설정 한 방법입니다.


fab 1.5부터 호스트를 동적으로 설정하는 문서화 된 방법입니다.

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

아래 문서에서 인용하십시오.

동적으로 설정된 호스트 목록과 함께 실행 사용

Fabric의 일반적인 중급에서 고급 사용 사례는 런타임시 대상 호스트 목록 조회를 매개 변수화하는 것입니다 (역할을 사용하지 않는 경우). execute는 다음과 같이 매우 간단하게 만들 수 있습니다.

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)

다른 답변과는 달리, 이다 수정하는 것이 가능 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 에서 전달 된 모든 항목이 사용됩니다.


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

To explain why it's even an issue. The command fab is leveraging fabric the library to run the tasks on the host lists. If you try and change the host list inside a task, you're esentially attempting to change a list while iterating over it. Or in the case where you have no hosts defined, loop over an empty list where the code where you set the list to loop over is never executed.

The use of env.host_string is a work around for this behavior only in that it's specifying directly to the functions what hosts to connect with. This causes some issues in that you'll be remaking the execution loop if you want to have a number of hosts to execute on.

The simplest way the people make the ability to set hosts at run time, is to keep the env populatiing as a distinct task, that sets up all the host strings, users, etc. Then they run the deploy task. It looks like this:

fab production deploy

or

fab staging deploy

Where staging and production are like the tasks you have given, but they do not call the next task themselves. The reason it has to work like this, is that the task has to finish, and break out of the loop (of hosts, in the env case None, but it's a loop of one at that point), and then have the loop over the hosts (now defined by the preceding task) anew.


You need to modify env.hosts at the module level, not within a task function. I made the same mistake.

from fabric.api import *

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

env.hosts = _get_hosts()

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

It's very simple. Just initialize the env.host_string variable and all of the following commands will be executed on this host.

from fabric.api import env, run

env.host_string = 'user@exmaple.com'

def foo:
    run("hostname -f")

I'm totally new to fabric, but to get fabric to run the same commands on multiple hosts (e.g. to deploy to multiple servers, in one command) you can run:

fab -H staging-server,production-server deploy 

where staging-server and production-server are 2 servers you want to run the deploy action against. Here's a simple fabfile.py that will display the OS name. Note that the fabfile.py should be in the same directory as where you run the fab command.

from fabric.api import *

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

This works with fabric 1.8.1 at least.


So, in order to set the hosts, and have the commands run across all the hosts, you have to start with:

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

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

Once those are defined, then run the command on the command line:

fab PROD deploy:1.5

What will run the deploy task across all of the servers listed in the PROD function, as it sets the env.hosts before running the task.


You can assign to env.hoststring before executing a subtask. Assign to this global variable in a loop if you want to iterate over multiple hosts.

Unfortunately for you and me, fabric is not designed for this use case. Check out the main function at http://github.com/bitprophet/fabric/blob/master/fabric/main.py to see how it works.


Here's another "summersault" pattern that enables the fab my_env_1 my_command usage:

With this pattern, we only have to define environments one time using a dictionary. env_factory creates functions based on the keynames of ENVS. I put ENVS in its own directory and file secrets.config.py to separate config from the fabric code.

The drawback is that, as written, adding the @task decorator will break it.

Notes: We use def func(k=k): instead of def func(): in the factory because of late binding. We get the running module with this solution and patch it to define the function.

secrets.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

Using roles is currently considered to be the "proper" and "correct" way of doing this and is what you "should" do it.

That said, if you are like most of what you "would like" or "desire" is the ability to perform a "twisted syster" or switching target systems on the fly.

So for entertainment purposes only (!) the following example illustrates what many might consider to a risky, and yet somehow thoroughly satisfying, manoeuvre that goes something like this:

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

Then running:

fab perform_sumersault

참고URL : https://stackoverflow.com/questions/2326797/how-to-set-target-hosts-in-fabric-file

반응형