[PHP] github webhook을 이용한 소스코드 서버 자동배포
- 우분투 16.04, git 2.7.4 환경을 기준으로 테스트 했습니다.
서비스에 사용되는 서버가 소수 일 때는 각 서버에 접속해서 git pull 명령어를 실행하면 됐지만 점점 더 많아지니까 소스 배포도 일이 되기에 github 를 이용해 소스코드를 서버에 자동 배포할 수 있는 환경을 구축하기로 했다. github 에서 제공하는 webhook 을 이용하면 문제를 해결할 수 있다. 환경 구축에 참고한 내용은 https://github.com/mboynes/github-deploy 이다.
#0. 준비
보통 웹서버는 www-data 등의 사용자로 실행이 된다. 웹루트의 소유자가 www-data 등의 사용자가 아니라면 퍼미션이 없기 때문에 바로 git pull 의 명령어를 실행시킬 수 없다. 이를 위해 www-data 사용자가 sudo 명령어를 사용할 수 있도록 수정했다. 배포 명령을 실행하는 deploy.php 파일은 웹루트에 위치한다.
# visudo
위 명령어를 실행한 후 아래 라인을 추가한다. www-data 사용자에게 비밀번호 입력없이 git 명령어를 사용할 수 있도록 한다.
www-data ALL=(ALL) NOPASSWD: /usr/bin/git
다음으로 각 서버에서 git 사용을 위한 기본적인 설정을 한다.
$ git config --global user.email "example@example.com"
$ git config --global user.name "Example"
$ git config --global push.default simple
위에서 이메일과 이름은 변경한다. deploy 로그를 기록하기 위해 deploy.php 파일의 상위 디렉토리에 logs 디렉토리를 생성하고 www-data 사용자가 파일을 쓸 수 있도록 퍼미션을 수정한다. 보통은 chmod 707 logs 와 같이 실행한다. logs 디렉토리 안에 deploy.log 파일을 생성해 둔다. 소유권은 www-data 에게 준다.
#1. git pull 명령어를 실행할 php 추가
<?php
# Array of the authorized IP addresses who can POST here. You can override this in your config if you so choose.
$authorized_ips = array(
'207.97.227.253',
'50.57.128.197',
'108.171.174.178',
'50.57.231.61',
'54.235.183.49',
'54.235.183.23',
'54.235.118.251',
'54.235.120.57',
'54.235.120.61',
'54.235.120.62'
);
# Put your deploy config file in the same dir as this file
if ( file_exists( dirname( __FILE__ ) . '/deploy-config.php' ) )
include_once( 'deploy-config.php' );
# A regex matching the ref of the "push". `git pull` will only run if this matches. Default is the master branch.
if ( !defined( 'REF_REGEX' ) )
define( 'REF_REGEX', '#^refs/heads/master$#' );
if ( !defined( 'LOG_WRITE' ) )
define( 'LOG_WRITE', true );
# Log location; make sure it exists
if ( !defined( 'LOG' ) )
define( 'LOG', '../logs/deploy.log' );
# Where is your repo directory? This script will chdir to it. If %s is present, it gets replaced with the repository name
if ( !defined( 'REPO_DIR' ) )
define( 'REPO_DIR', dirname( __FILE__ ) . "/" );
# Where is your git binary, and what command would you like to run?
if ( !defined( 'GIT_COMMAND' ) )
define( 'GIT_COMMAND', 'git pull' );
# Do we want to do IP verification?
if ( !defined( 'VERIFY_IP' ) )
define( 'VERIFY_IP', true );
# If defined, $_POST gets logged
# define( 'DUMP_POSTDATA', true );
# In your webhook URL to github, you can append ?auth={{ this field }} as a very simple gut-check authentication.
# define( 'AUTH_KEY', 'whatever-you-want' );
if ( is_writable( LOG ) && $handle = fopen( LOG, 'a' ) ) {
# Sweet taste of victory
if(LOG_WRITE)
fwrite( $handle, date( 'Y-m-d H:i:s' ) . "\n==============================\n" );
else
@fclose( $handle );
} else {
@fclose( $handle );
header( $_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500 );
die( 'Please complete installation' );
}
# Do some authentication
if ( defined( 'AUTH_KEY' ) && ( !isset( $_GET['auth'] ) || AUTH_KEY != $_GET['auth'] ) ) {
$error = "Auth key doesn't match";
} elseif ( !isset( $_POST['payload'] ) ) {
$error = '$_POST["payload"] is not set';
} elseif ( VERIFY_IP && !in_array( $_SERVER['REMOTE_ADDR'], $authorized_ips ) ) {
$error = "{$_SERVER['REMOTE_ADDR']} is not authorized. Authorized IPs are: " . implode( ', ', $authorized_ips );
} else {
$error = false;
}
if ( false !== $error ) {
if(LOG_WRITE)
fwrite( $handle, "*** ALERT ***\nFailed attempt to access deployment script!\n\nMESSAGE: $error\n\n" . print_r( $_SERVER, 1 ) . print_r( $_REQUEST, 1 ) . "\n\n\n" );
@fclose( $handle );
header( $_SERVER['SERVER_PROTOCOL'] . ' 401 Unauthorized', true, 401 );
die( "You don't have permission to access this page." );
}
# We're authorized, let's do this!
if(LOG_WRITE) {
$content = '';
if ( defined( 'DUMP_POSTDATA' ) )
$content .= print_r( $_POST, 1 ) . "\n\n";
if ( false === fwrite( $handle, $content ) ) {
echo "Couldn't write to log!\n";
}
}
$payload = json_decode( $_POST['payload'] );
if ( preg_match( REF_REGEX, $payload->ref ) ) {
# If we have a commit to master, we can pull on it
# If commit message has [deployment], pull
$cnt = count($payload->commits);
$is_deploy = false;
for($i=0; $i<$cnt; $i++) {
if(preg_match('#\[deployment\]#i', $payload->commits[$i]->message)) {
$is_deploy = true;
break;
}
}
if($is_deploy === true) {
$userInfo = posix_getpwuid(fileowner(__FILE__));
$owner = $userInfo['name'];
$output = array( 'bash> ' . sprintf(GIT_COMMAND, $owner) );
chdir( sprintf( REPO_DIR, $payload->repository->name ) );
exec( sprintf(GIT_COMMAND, $owner) . ' 2>&1', $output );
if(LOG_WRITE)
fwrite( $handle, "`$payload->ref` matches, executing:\n" . sprintf(GIT_COMMAND, $owner) . "\n" . implode( "\n", $output ) . "\n" );
}
} else {
if(LOG_WRITE)
fwrite( $handle, "`$payload->ref` doesn't match the ref criteria\n" );
}
if(LOG_WRITE)
fwrite( $handle, date( 'Y-m-d H:i:s' ) . " Over and out!\n\n\n" );
@fclose( $handle );
?>
deploy.php 의 코드는 위와 같다. 그리고 아래는 deploy-config.php 의 코드이다. 마킹된 95 ~ 105라인의 코드에서 커밋 메세지 중 [deployment] 라는 문구가 포함되어 있으면 git pull 명령을 실행하도록 한다.
<?php
# A regex matching the ref of the "push". <code>git pull</code> will only run if this matches. Default is the master branch.
define( 'REF_REGEX', '#^refs/heads/master$#' );
# Write Log File
define('LOG_WRITE', false);
# Log location; make sure it exists
define( 'LOG', '../logs/deploy.log' );
# Where is your repo directory? This script will chdir to it. If %s is present, it gets replaced with the repository name
define( 'REPO_DIR', dirname( __FILE__ ) . "/" );
# If set to true, $_POST gets logged
define( 'DUMP_POSTDATA', false );
# In your webhook URL to github, you can append ?auth={{ this field }} as a very simple gut-check authentication
define( 'AUTH_KEY', 'some_auth_key' );
# Where is your git binary, and what command would you like to run?
define( 'GIT_COMMAND', 'sudo -u %s /usr/bin/git pull' );
# Do we want to do IP verification?
define( 'VERIFY_IP', false );
#2. webhook 등록
github.com 에서 repo 설정에 webhook 을 등록한다. webhook 등록 때 url은 아래와 같은 형식으로 등록해야 한다.
http://example.com/deploy.php?auth=some_auth_key
위의 과정을 거친 후 commit 메세지에 [deployment] 문구가 들어가면 webhook 이 실행되어 git pull 명령을 실행하게 된다. 제대로 실행되지 않는다면 log 파일의 내용을 확인해서 조치한다.