首发于Airbnb日记

利用git push向服务器一键部署代码

不知大家在做一些需要服务器的小项目的时候都是怎么部署代码的?我之前在Heroku的时候就觉得他家的代码部署方法及其方便,基本上就是

heroku apps:create my_app
git push heroku master

然后就一路帮你部署到heroku的平台。绝对是2分钟上线一个Rails网站。

于是我这两天就在研究如何用git push部署代码,发现网上的大部分解决方案都是基于git checkout的。我不太喜欢这个方法,因为用这个方法push后,真正的代码属于漂浮的状态,当你运行git status的时候会发现那些新加的代码依然处于unstaged状态。

我下面要介绍的是基于git reset --hard的push-to-deploy设置方案

首先你需要一个服务器,这个服务器可以是你家里的电脑也可以是云端的服务器。你必须可以顺利ssh进去。至于怎么ssh,这不是本文要讨论的内容(大家可以学习ssh-keygen、authorized_keys、man ssh-config的相关内容)。

假如你能顺利的ssh进你的服务器如

ssh ubuntu@myserver.com

那么要做的事情如下

设置文件夹

进入本地git文件夹,然后将代码push到一个远端新建的裸代码库。

cd local_git_code
git remote add stg ubuntu@myserver.com:my_repo
ssh ubuntu@myserver.com 'cd ~ && git init --bare my_repo;'

什么是裸代码库?基本上就是那些github之类的网站存储你的代码的形式。这样的代码库只有历史记录和索引,没有实际以文件形式存在的代码。那些代码都以二进制的形式存在git自己的数据库里。

有了代码库以后就可以设置自己的自动部署挂钩了。这个挂钩本质是一个脚本,当这个文件在repo/hooks/文件夹内,名字为post-receive并且ubuntu的x属性(可执行)为真的时候,git会在收到push后执行它。

于是你就应该将下面这个脚本稍加修改放到本地的git工作文件夹中,可以取名post-receive.hook。注意,这个文件必须为可执行状态,可以用

ls -l .

查看。可以使用

chmod u+x post-receive.hook

保证其可执行型。

#!/bin/sh

# This file is to be used by git repo on server
# It should be a file called hooks/post-receive in the bare git repo

set -xe  # x表示是打印每一个运行的命令;e表示有任何错误就退出

echo "Running Post Receive Hook"

export GIT_WORK_TREE=$HOME/my_code
export GIT_DIR=${GIT_WORK_TREE}/.git

cd ${GIT_WORK_TREE}
git fetch origin
git reset --hard origin/master

# reload my apps
# pkill -HUP uwsgi

你可以在文件的尾部加一些应用重置命令(如果你用uwsgi运行python应用的话就可以用如上所示案例)。

将此文件加入git后push到远端服务器

git add post-receive.hook
git commit -m "Add post receive hook"
git push stg master

下一步是在服务器checkout出一份代码

# In remote server
cd ~
git clone my_repo my_code

这个时候就会出现my_repo文件夹,里面会有你的代码。同时,这个代码文件夹的默认origin远端(remote)就会变成my_repo文件夹。当你做git fetch git pull等动作的时候就会从my_repo取信息。

下一步是将挂钩脚本放入repo中

ln -s ~/my_code/post-receive.hook ~/my_repo/hooks/post-receive

注意,这个文件链接一定要放在hooks文件夹内,名字必须叫post-receive

这个时候基本就设置好了

push-to-deploy

下面就可以试验push-to-deploy的效果了。在本地文件夹

touch README.md
git add README.md
git commit
git push stg master

你将会看到一系列的消息

技术细节

你会注意到,post-receive脚本内有这两行

export GIT_WORK_TREE=$HOME/my_code
export GIT_DIR=${GIT_WORK_TREE}/.git

这是告诉git,当你运行的时候所有跟代码本身有关的操作都对my_code进行,所有跟git索引和数据库有关的操作都基于my_code/.git下面。

网上很多现有的方案都是用得checkout -f,他们都不改变GIT_DIR变量。这就导致了my_code/.git并不更新,就像是把my_repo拿过来变成.git然后做一堆checkout操作,然后再恢复原来的.git文件夹,所以所有的新代码都处于unstaged状态。

本文提到的这样做的好处是,你可以利用git push -f进行一些试验性的操作。加入你修改了一个本地文件,你需要部署到staging服务器,那么你就可以用

git add my_changed_file
git commit -m "informal commit"
git push stg master

这个时候你发现不好用又做了一次修改,你可以直接覆盖+暴力推送,而不产生一个新的提交(commit)

git add my_changed_file
git commit --amend --no-edit
git push -f stg master

等到你完全满意了以后,你可以正式推送到官方代码库和production服务器

git commit --amend -m "Real commit"
git push -f stg master
git push origin master  # like github.com
git push prod master    # if you have this remote

这样的话你的那些修改的中间状态就不会进到官方的代码库,不会被别人看到。

当然,对production服务器和主代码库做暴力推送是不提倡的,在有多人在开发的时候应该是禁止的。

以上。

我的github: github.com/zhongjiewu 如有问题可以留在评论里。

编辑于 2014-10-15 12:08