利用Fabric+Capistrano实现Python自动化部署

Fabric是一个用于应用(批量)部署和系统(批量)管理的Python库和命令行工具,关于Fabric的介绍请参考:http://www.fabfile.org/

Capistrano是一个用Ruby语言编写的远程服务器自动化和部署工具,关于Capistrano的介绍请参考:http://capistranorb.com/

本文仅使用Python语言和部分Linux或Windows系统命令,借助Fabric模块和Capistrano的部署思路,实现在Linux平台和Windows平台的自动化部批量署应用或实现批量系统管理(批量执行命令,批量上传文件等),其中Fabric部分利用Fabric的模块,Capistrano部分用Python语言按照Capistrano的部署思路“重写(Python实现Capistrano)”。

关于Capistrano的“重写”说明。Capistrano使用Ruby语言写的,在部署很多应用上有很大的优势,个人认为它设计最好的部分就是它的目录结构。目录结构的详细信息可以参考:http://capistranorb.com/documentation/getting-started/structure/#。有了这个目录结构可以轻松实现每一个部署版本的备份与回滚,之前用Bash Shell“重写”过一次,可以参考本文《Linux Shell脚本之远程自动化部署java maven项目 》,这次相当于用Python重写一下(Capistrano还有大量精髓的东西,本文算是抛砖引玉,其他的日后再发掘),毕竟Shell脚本不容易实现像Fabric那样的批量操作。

本文的demo是将https://github.com/DingGuodong/GoogleHostsFileForLinux.git 中的用于翻墙访问Google的脚本上传到指定的服务器,以在Windows操作为例,先在本地生成Capistrano目录结构,再把git项目clone到本地,将脚本文件从repo目录下抽出,放到current目录下,current是release目录下某个时间戳的软链接(Windows下测试可能有些奇怪,因为Windows下没法做软连接,用Python创建快捷方式暂时没有找到方法),再将此脚本通过Fabric的put命令上传到指定的远程服务器上。

demo在脚本中的位置可以从TODO中找到,由于fabric需要通过fab命令+脚本执行,因此在脚本的最后使用了terminal_debug()函数实现脚本执行,如果对python和fabric很熟悉,那么将下文的脚本放到pycharm中打开,略微看看脚本,即时没有注释(有人曾说,好的代码是不写注释的,虽然代码写的不好,但至少要朝着这个目标努力)也能看的很明白。其实在真正了解了Fabric和Capistrano后,重新阅读这个脚本或者看这篇文章一定觉得确实写的很普(渣)通(渣)。

脚本的部分说明(时间原因就不展开写了,可以等熟悉了Fabric和Capistrano后再看):

  1. 此脚本文件的开始几行配置有名字为config的字典,主要用于声明deploy_to、repo_url和branch以及keep_releases;
  2. env变量用于向Fabric声明主机信息。

运行结果:

与Capistrano相同的目录结构:

模仿Capistrano生成的相同格式的日志:

脚本内容可以从GitHub上获取:https://github.com/DingGuodong/LinuxBashShellScriptForOps/blob/master/projects/autoOps/pythonSelf/pyCapistrano.py

脚本内容如下:

#!/usr/bin/python
# encoding: utf-8
# -*- coding: utf8 -*-
"""
Created by PyCharm.
File:               LinuxBashShellScriptForOps:TestGit.py
User:               Guodong
Create Date:        2016/8/24
Create Time:        9:40
 """
from fabric.api import *
from fabric.main import main
from fabric.colors import *
from fabric.context_managers import *
from fabric.contrib.console import confirm
import os
import sys
import re
import getpass

config = {
    "deploy_to": ‘/var/www/my_app_name‘,
    "scm": ‘git‘,
    "repo_url": ‘https://github.com/DingGuodong/GoogleHostsFileForLinux.git‘,
    "branch": ‘master‘,
    "log_level": ‘debug‘,
    "keep_releases": 10
}

env.roledefs = {
    ‘test‘: [‘[email protected]:22‘, ],
    ‘nginx‘: [‘[email protected]:22‘, ‘[email protected]:22‘, ],
    ‘db‘: [‘[email protected]:22‘, ‘[email protected]:22‘, ],
    ‘sit‘: [‘[email protected]:22‘, ‘[email protected]:22‘, ‘[email protected]:22‘, ],
    ‘uat‘: [‘[email protected]:22‘, ‘[email protected]:22‘, ‘[email protected]:22‘, ],
    ‘all‘: ["10.6.28.27", "10.6.28.28", "10.6.28.35", "10.6.28.46", "10.6.28.93", "10.6.28.125", "10.6.28.135"]
}

env.user = "root"
env.hosts = ["10.6.28.27", "10.6.28.28", "10.6.28.35", "10.6.28.46", "10.6.28.93", "10.6.28.125", "10.6.28.135"]

def win_or_linux():
    # os.name ->(sames to) sys.builtin_module_names
    if ‘posix‘ in sys.builtin_module_names:
        os_type = ‘Linux‘
    elif ‘nt‘ in sys.builtin_module_names:
        os_type = ‘Windows‘
    return os_type

def is_windows():
    if "windows" in win_or_linux().lower():
        return True
    else:
        return False

def is_linux():
    if "linux" in win_or_linux().lower():
        return True
    else:
        return False

class Capistrano(object):
    class SCM(object):
        class Git(object):
            def __init__(self):
                self.repo_url = None
                self.name = None
                self.branch = None
                self.repo_path = None
                self.user = None

            def set(self, repo_url, branch=None, repo_path=None):
                if repo_url is None:
                    abort("You must specify a repository to clone.")
                else:
                    self.repo_url = repo_url
                if branch is None:
                    self.branch = "master"
                else:
                    self.branch = branch

                pattern = re.compile(r"(\w+)(?=\.git$)")
                match = pattern.search(repo_url)
                if match:
                    paths = match.group()
                else:
                    paths = None
                if repo_path is not None and not os.path.exists(repo_path):
                    try:
                        os.mkdir(repo_path)
                    except IOError:
                        repo_path = os.path.join(os.path.dirname(__file__), paths)
                elif repo_path is None:
                    repo_path = ""
                self.repo_path = os.path.abspath(repo_path)

            def clone(self):
                local("git clone --branch %s %s %s" % (self.branch, self.repo_url, self.repo_path))

            def check(self):
                with lcd(self.repo_path):
                    return local("git ls-remote --heads %s" % self.repo_url, capture=True)

            def pull(self):
                with lcd(self.repo_path):
                    if os.path.exists(os.path.join(self.repo_path, ".git")):
                        local("git pull origin %s" % self.branch)
                    else:
                        self.clone()
                        self.pull()

            def update(self):
                pass

            def status(self):
                with lcd(self.repo_path):
                    local("git status")

            def branch(self):
                with lcd(self.repo_path):
                    local("git rev-parse --abbrev-ref HEAD", capture=True)

            def long_id(self):
                with lcd(self.repo_path):
                    return local("git rev-parse HEAD", capture=True)

            def short_id(self):
                with lcd(self.repo_path):
                    return local("git rev-parse --short HEAD", capture=True)

            def fetch_revision(self):
                with lcd(self.repo_path):
                    return local("git rev-list --max-count=1 %s" % self.branch, capture=True)

            def user(self):
                if is_linux():
                    self.user = "%s(%s)" % (os.getlogin(), os.getuid())
                if is_windows():
                    import getpass
                    self.user = getpass.getuser()

    class DSL(object):
        class Paths(object):
            def __init__(self):
                self.deploy_to = config[‘deploy_to‘]
                self.current = None

            # TODO(Guodong Ding) fetch ‘deploy_to‘ from config file or dict
            def deploy_path(self):
                return os.path.abspath(self.deploy_to)

            def current_path(self):
                current_directory = "current"
                return os.path.join(self.deploy_path(), current_directory)

            def releases_path(self):
                return os.path.join(self.deploy_path(), "releases")

            def set_release_path(self):
                import datetime
                timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
                self.current = os.path.join(self.releases_path(), timestamp)
                return os.path.join(self.releases_path(), timestamp)

            def shared_path(self):
                return os.path.join(self.deploy_path(), "shared")

            def repo_path(self):
                return os.path.join(self.deploy_path(), "repo")

            def revision_log(self):
                return os.path.join(self.deploy_path(), "revisions.log")

            def __paths(self):
                return self.releases_path(), self.repo_path(), self.shared_path()

            def makepaths(self):
                for directory in self.__paths():
                    if not os.path.exists(directory):
                        os.makedirs(directory)

            def make_release_dirs(self):
                os.makedirs(self.set_release_path())

            def make_current(self):
                if is_linux():
                    if os.path.exists(self.current_path()) and os.path.islink(self.current_path()):
                        os.unlink(self.current_path())
                    os.symlink(self.current, self.current_path())
                if is_windows():
                    if os.path.exists(self.current_path()):
                        import shutil
                        shutil.rmtree(self.current_path())
                    try:
                        local("ln -sd %s %s" % (self.current, self.current_path()))
                    except Exception:
                        raise NotImplementedError

            def update_revision_log(self, branch=None, sid=None, release=None, by=None):
                print blue("Log details of the deploy")
                with open(self.revision_log(), ‘a‘) as f:
                    f.write("Branch %s (at %s) deployed as release %s by %s\n" % (branch, sid, release, by))

            def cleanup(self):
                keep_releases = config[‘keep_releases‘]
                releases = local("ls -xtr %s" % self.releases_path(), capture=True).split()
                # print releases[-keep_releases:]
                if len(releases) > keep_releases:
                    for release in releases[0:(len(releases) - keep_releases)]:
                        local("rm -rf %s" % os.path.join(self.releases_path(), release))

            @staticmethod
            def __get_file_last_line(inputfile):
                filesize = os.path.getsize(inputfile)
                blocksize = 1024
                with open(inputfile, ‘rb‘) as f:
                    last_line = ""
                    if filesize > blocksize:
                        maxseekpoint = (filesize // blocksize)
                        f.seek((maxseekpoint - 1) * blocksize)
                    elif filesize:
                        f.seek(0, 0)
                    lines = f.readlines()
                    if lines:
                        lineno = 1
                        while last_line == "":
                            last_line = lines[-lineno].strip()
                            lineno += 1
                    return last_line

            def rollback(self):
                print blue("Revert to previous release timestamp")
                revision_log_message = self.__get_file_last_line(self.revision_log())
                last_release = None
                import re
                s = re.compile(r"release (.*) by")
                match = s.search(revision_log_message)
                if match:
                    last_release = match.groups()[0]
                else:
                    abort("Can NOT found rollback release in revision log files, %s." % self.revision_log())
                if os.path.exists(last_release):
                    print yellow("Symlink previous release to current")
                else:
                    abort("Can NOT found rollback release on filesystem.")
                if is_linux():
                    if os.path.exists(self.current_path()) and os.path.islink(self.current_path()):
                        os.unlink(self.current_path())
                    os.symlink(last_release, self.current_path())
                if is_windows():
                    if os.path.exists(self.current_path()):
                        import shutil
                        shutil.rmtree(self.current_path())
                    try:
                        local("ln -sd %s %s" % (last_release, self.current_path()))
                    except Exception:
                        raise NotImplementedError

    class Application(object):
        class Deploy(object):
            def __init__(self):
                self.P = Capistrano.DSL.Paths()
                self.G = Capistrano.SCM.Git()

            def deploy(self):
                # TODO(Guodong Ding): core job here, this is a deploy demo
                with lcd(self.P.current_path()):
                    try:
                        src = os.path.join(self.P.repo_path(), "replaceLocalHostsFileAgainstGfw.sh")
                        local_path = os.path.join(self.P.current_path(), "hosts")
                        remote_path = "/tmp/replaceLocalHostsFileAgainstGfw.sh"
                        with open(src, ‘r‘) as f:
                            content = f.read()
                        with open(local_path, "w") as f:
                            f.write(content)
                        if os.path.getsize(local_path):
                            print red("upload files to remote hosts")
                            put(local_path, remote_path)
                            run("chmod +x %s" % remote_path)
                            run("ls -al %s" % remote_path)
                            print red("deploy test demo successfully!")
                    except IOError:
                        raise NotImplementedError

            def run(self):
                print blue("Do deploy procedure.")
                self.P.makepaths()
                self.G.set(config["repo_url"], repo_path=self.P.repo_path())
                self.G.pull()

                self.P.make_release_dirs()
                self.P.make_current()
                self.deploy()
                self.P.update_revision_log(self.G.branch, self.G.short_id(), self.P.current, getpass.getuser())
                self.P.cleanup()
                print green("Deploy successfully!")

@roles("test")
def test_deploy():
    c = Capistrano.Application.Deploy()
    c.run()

def terminal_debug(defName):
    command = "fab -i c:\Users\Guodong\.ssh\exportedkey201310171355                -f %s                 %s" % (__file__, defName)
    os.system(command)
    sys.exit(0)

if __name__ == ‘__main__‘:
    if len(sys.argv) == 1 and is_windows():
        terminal_debug("test_deploy")

    sys.argv[0] = re.sub(r‘(-script\.pyw|\.exe)?$‘, ‘‘, sys.argv[0])
    print red("Please use ‘fab -f %s‘" % " ".join(str(x) for x in sys.argv[0:]))
    sys.exit(1)

关于Fabric的进一步使用可以参考GitHub上的另一个文件:https://github.com/DingGuodong/LinuxBashShellScriptForOps/blob/master/projects/autoOps/pythonSelf/fabfile.py 此文件有更多关于Fabric的示例,可以更好的说明Fabric的用途。

最后,不得不说Python是优秀的编程、脚本语言,用在运维上确实很方便,只需短短的几天时间就可以编写出有用的脚本。作为运维人员不必排斥编程,编程是为了更好的运维。如果觉得本文有用,可以继续关注这个GitHub项目(https://github.com/DingGuodong/LinuxBashShellScriptForOps),这个项目会持续完善,积累更多有用的Shell、Python编程和运维的相关知识和文件。

--end--

时间: 2024-10-02 19:09:36

利用Fabric+Capistrano实现Python自动化部署的相关文章

Jenkins+GitLab+dotnet+Python自动化部署.Net Core项目

部署环境与流程1) Jenkins是java产品,需安装JDK8.由于.netFreamwork项目自动化时是基于Windows,所以继续使用Windows server 2012环境下的已有的Jenkins,部署.构建dotnet Core项目继续在Windows平台下操作.2) .NET Core SDK:2.2.402.dotnet build构建.3) 代码仓库GitLab.4) .NET Core服务端CentOS7部署环境流程: 需求一个项目下分两个子项目,而子项目需分别进行构建部署

python 自动化部署工具Fabric简介

自动化部署工具Fabric简介 Fabric就是一个帮助我们在上线时减少重复/繁琐操作的自动化部署利器,对于缺乏成熟运维平台的众多小公司的运维或开发人员来说,掌握这个工具是有必要的. 1. Fabric是什么 Fabric官方文档的描述如下:      Fabric is a Python (2.5-2.7) library and command-line tool for streamlining the use of SSH for application deployment or sy

基于fabric和hg的自动化部署

自动化部署 fabric是个很好用的自动化部署工具,虽然功能比起puppet,saltstack之类要弱一些,但胜在用python,而且免安装服务端. 当然你要说docker更好我也同意,然而我是经常使用FreeBSD的,而且还有一些32位的低配系统,并不适合用docker.更不用说虚拟机了. 自动化部署的目的主要是简化手工部署的麻烦,包括初次安装部署和代码修改后的更新部署.初始部署主要是安装基础环境,初始化数据库等.更新部署则更麻烦一些,需要修改基础环境配置,变更数据库结构等.相比之下代码发布

利用jenkins做项目的自动化部署

最近领导要求上海本地的项目需要使用进jenkins实现自动化部署,以便可以直接将项目移交给运维的同学,减轻开发的工作量.记录下这次爬坑的过程. 一.前言 Jenkins是基于Java开发的一种持续集成工具,用于监控持续重复的工作,功能包括: 1.持续的软件版本发布/测试项目. 2.监控外部调用执行的工作. 上面是我从百科上down下来的,老实说没看懂,这是个什么玩意啊?其实以我现在的理解和应用,最多的便是部署了,其他功能待研究╮(╯_╰)╭ 撸主目前在上海一个不知名国企打工,我们现在项目的发布流

利用Powershell在IIS上自动化部署网站

本文主要讲如何通过Powershell在IIS上自动化部署ASP.NET网站,而不涉及Powershell的基本语法,如果没有Powershell基础的同学也可以把本文作为学习Powershell的基石,通过学习本文中的脚本再去查阅具体的语法,可能会达到事半功倍的效果. 一般我们建立网站大致需要以下几个步骤: 1.安装.NET Framework 2.安装了IIS 3.注册.启用ISAPI和CGI限制 4.建立网站 5.设置默认首页.身份验证.设置MIME类型 6.绑定域名或IP地址 7.设置权

Python自动化部署

# -*- coding: utf-8 -*- #!/bin/env python ''' #Auth: tyk #Function: released version #Date:2017/6/27 #Version:V1.0 ''' import  sys,re,os,time,datetime import  paramiko import logging import socket import ConfigParser import traceback from progressbar

利用fabric自动化发布代码

这几天,在测试环境开发python程序,但是生产的测试环境上和测试环境不一样,发布的程序涉及到3个角色,node,web和monitor,里面的配置文件要根据生产环境进行替换,用手工替换几次以后,感觉太过麻烦了,就想利用fabric进行编写脚本自动化.为什么想到用fabric?因为入门简单,文档多! 脚本的思路大致说下: 1)在本地服务器通过svn checkout命令把最新的代码,下载下来! 2)然后对里面的配置文件进行个性化的替换,然后打包成 tar.gz形式 3)分发到不同角色的服务器上,

<zz>PYTHON FABRIC实现远程操作和部署

from http://www.cnblogs.com/liujianzuo888/articles/6230691.html fabric title是开发,但是同时要干开发测试还有运维的活 (o(╯□╰)o) 近期接手越来越多的东西,发布和运维的工作相当机械,加上频率还蛮高,导致时间浪费还是优点多. 修复bug什么的,测试,提交版本库(2分钟),ssh到测试环境pull部署(2分钟),rsync到线上机器A,B,C,D,E(1分钟),分别ssh到ABCDE五台机器,逐一重启(8-10分钟)

fabric自动化部署django

使用fabric部署django应用 使用fabric部署django应用 本文是我的网站易读中文网自动化部署的脚本实现,以下代码在ubuntu和debian中测试通过 由于网站使用的是python技术,鉴于python的强大,在自自动部署上有fabric这个一个强大的工具,阅读本文除了略懂python,fabric,还需要安装fabtools,安装这个是为了简化一些基本的操作(为不太懂linux的人找的借口而已),里面封装了非常多的命令.下面我一步一步的将我网站的自动部署过程写下来 首先是fa