[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 파일의 내용을 확인해서 조치한다.

편리

PHP와 MariaDB, jQuery 등을 사용해 게시판, 쇼핑몰 솔루션을 개발합니다. 그누보드5와 영카트5 개발에 참여 했습니다. Linux와 Nginx는 물론 WordPress, Git 등에도 관심이 많습니다. 자전거 타기 및 사진 촬영을 취미로 하고 있습니다.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.