11
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

CYBIRDエンジニアAdvent Calendar 2015

Day 25

Capistrano3でJenkinsサーバーからS3経由でWebサーバーにソースコードを配布してみた話

Last updated at Posted at 2015-12-25

初めに

こんにちは。CYBIRDエンジニア Advent Calendar最終日担当の@jin_kです。
CYBIRD2015年の新卒入社でゲームインフラ部という部署に所属しております。

24日目は@gotyooooさんのAmazon VPC NAT Gatewayがあるとき。ないとき。でした。
Amazon VPC NATGatewayがあるときとないときが分かりやすく整理されていて、NAT Gatewayを使うと何か良いか書いていますので、ぜひご覧ください!

##内容について
今回はJenkinsサーバーからS3経由でWebサーバーにソースコードを配布してみた話をしたいと思います。
(Jenkinsサーバーを立てる過程の説明とかはありません。)

従って今回の話はちょっと変わったCapistranoの話をするので、Capistranoのデプロイ流れや説明・使い方についての話はしません。
とはいえ、自分もまだ初心者なので何か間違ったことが書いてある場合、ご指摘お願いします。
##今回のCapistranoの動き
###このような動きをした理由

  • Capistrano3系でGithubとの連携がHTTPSでアクセスするため、ソースコードにgitのアカウント情報が載せているのでセキュリティー的に良くないです。
  • 各自WebサーバーがダウンロードすることでJenkinsのサーバーの負担を減らす。
  • いずれにせよ100%信頼はできないが、それでもgitよりS3の方が信頼できる。

###仕組み(pull)
스크린샷 2015-12-22 15.41.43.png
1.gitとJenkinsを連携し、最新のfileをJenkinsに返却します。
 (今回は1番に関する内容はありません。)
2.cap実行し、tarでソースコードを固めます。
3.固めたtarをS3にアップロードします。
(現在デプロイするソースコードと同じものがS3にある場合、3番のことはしません。)
4.各自S3からダウンロードするようWebサーバーに命令します。
5.各自Webサーバーがs3からtarをダウンロードします。
6.ダウンロードしたtarを展開します。
###仕組み(push)
스크린샷 2015-12-22 19.19.00.png

1.gitとJenkinsを連携し、最新のfileをJenkinsに返却します。
 (今回は1番に関する内容はありません。)
2.cap実行し、tarでソースコードを固めます。
3.JenkinsサーバーからWebサーバーにtarを送りつけます。
4.tarを展開します。

※pull以外はすべてpushの処理をします。
##インストール

  • archive/tar/minitar 0.5.2
$ gem install archive-tar-minitar -v 0.5.2
  • Ruby 2.1.0p0
$ rvm install ruby-2.1.0
$ rvm use ruby-2.1.0
$ ruby --version
ruby 2.1.0p0 (2013-12-25 revision 44422) [x86_64-linux]
  • Capistrano 3.2.1
$ gem install capistrano
$ cap --version
Capistrano Version: 3.2.1 (Rake Version: 10.1.0)

S3へのアップロードするためにRubyのAWS SDKを利用します。

  • aws-sdk-core 2.1.16
$ gem install aws-sdk-core -v 2.1.16
  • aws-sdk-resources 2.1.16
$ gem install aws-sdk-resources -v 2.1.16

S3経由でデプロイするためAWS CLIが必要となりますのでWebサーバーにinstallします。
CredentialFileはJenkinsサーバー、Webサーバー両方必要です。

# yum -y install python-setuptools
# easy_install pip
# pip install awscli
# vi /etc/aws/credential-file
====
[default]
output = json
region = ap-northeast-1
aws_access_key_id = XXXXXXXXXXXXXXXXXXXX
aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
====

デプロイ方法

###ソースコード

Capfile
$:.unshift File.expand_path(File.dirname(__FILE__) + '/lib')

# Load DSL and Setup Up Stages
require 'capistrano/setup'

# Includes default deployment tasks
require 'capistrano/deploy'

Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
config/deploy.rb
# config valid only for Capistrano 3.1
set :pty, true
lock '3.2.1'
set :log_level, SSHKit::Logger::INFO
set :application, 'JkTest'
set :scm, :tarball_deploy

set :tmp_dir, '/tmp'
set :copy_exclude, ['docs']

set :project_release_id, `git log --pretty=format:'%h' -n 1 HEAD`

#(省略)
config/deploy/production_pull.rb
set :deploy_to, ENV['DEPLOY_TO']#Webサーバーにソースコードを置くところです。
set :resource_path, ENV['RESOURCE_PATH']#以下のJenkinsサーバーにあるものをtarで固めます。

set :tmp_tarball, '/tmp/production'
set :aws_credential_file, ENV['AWS_CREDENTIAL_FILE']#awsにアクセスするためにcredential-fileが書いているところです。

servers_str = ENV['SERVERS'].to_s
servers_str = servers_str.gsub(/\s/, '')

servers_str.split(',').each do |target_server|
    if target_server.empty?
        next
    end
    server target_server,
        user: ENV['WEB_SERVER_USER'],#Webサーバーのユーザー
        roles: ['app'],
        ssh_options: {
            keys: [ENV['WEB_SERVER_SSH']],#Webサーバーにアクセスするためのsshが置いてある場所
            forward_agent: false,
            auth_methods: ['publickey']
        }
end
config/deploy/production_push.rb
set :deploy_to, ENV['DEPLOY_TO']
set :resource_path, ENV['RESOURCE_PATH']

set :tmp_tarball, '/tmp/production'
servers_str = ENV['SERVERS'].to_s
servers_str = servers_str.gsub(/\s/, '')

servers_str.split(',').each do |target_server|
    if target_server.empty?
        next
    end
    server target_server,
        user: ENV['WEB_SERVER_USER'],
        roles: ['app'],
        ssh_options: {
            keys: [ENV['WEB_SERVER_SSH']],
            forward_agent: false,
            auth_methods: ['publickey']
        }
end
lib/capistrano/tarball_deploy.rb
require 'digest/md5'
require 'zlib'
require 'archive/tar/minitar'
require 'aws-sdk-core'

namespace :tarball_deploy do

    s3_samefile_check = false#今デプロイするソースコードがS3に同じものがあるかcheckする変数

    if !ENV['DEPLOY_TYPE'].nil?
        # ENVで渡ってきていれば値を上書きする
        set :deploy_type, ENV['DEPLOY_TYPE']
    end

    desc 'check ENV value and configuration'
    task :check do
        if fetch(:tmp_tarball).nil?
            set :tmp_tarball, ENV['TMP_TARBALL'] ? ENV['TMP_TARBALL'] : '/tmp'
        end

        run_locally do
            execute "mkdir -p #{fetch(:tmp_tarball).gsub(/\/$/, '')}"
        end
        on roles(:app) do
            unless test 'which md5sum'
                error 'command `md5sum\' is not found.'
                abort
            end
        end

        if fetch(:deploy_type) == 'pull'
            # check AWS configuration
            #(省略)
        end
    end

ENVで渡している環境変数をcheckします。

lib/capistrano/tarball_deploy.rb

#(省略)
            # tarをアップロード
            if fetch(:deploy_type) == 'pull'
                unless (s3_samefile_check)
                    run_locally do
                        upload_tarball_to_s3(tarball)
                    end
                end
            else
                # pull以外はすべてpush型
                on roles(:app) do
                    upload! tarball, fetch(:tmp_dir)
                end
            end

tarをS3にアップロードする関数実行

lib/capistrano/tarball_deploy.rb
        on roles(:app) do
            if fetch(:deploy_type) == 'pull'
                # S3からダウンロードする処理
                execute "export AWS_CONFIG_FILE=#{fetch(:aws_credential_file)} && aws s3 cp s3://#{fetch(:bucket_name)}/#{fetch(:s3_path) + File.basename(tarball)} #{fetch(:tmp_dir)}/#{File.basename(tarball)}"
            end

            # check tarball hash
            #(省略)

S3からダウンロードします。

lib/capistrano/tarball_deploy.rb
    desc 'create tarball'
    file 'create_resource_tarball' do
        # create tarball on Capistrano installed server
        run_locally do
            if fetch(:deploy_type) == 'pull'
                Aws.config[:credentials] = Aws::SharedCredentials.new(
                path: fetch(:aws_credential_file),
                profile_name: fetch(:aws_profile_name)
                )
                Aws.config[:region] = fetch(:region)
                s3 = Aws::S3::Client.new()
                resp = s3.list_objects(bucket: fetch(:bucket_name),prefix: fetch(:s3_path))
                resp.contents.each do |object|
                    if ("#{fetch(:s3_path)}release-#{fetch(:project_release_id)}.tar.gz" == "#{object.key}")
                        s3_samefile_check = true
                    end
                end
            end

            unless (s3_samefile_check)
                info 'start to create tarball...'
                Zlib::GzipWriter.open("#{fetch(:tmp_tarball)}/release-#{fetch(:project_release_id)}.tar.gz", Zlib::BEST_COMPRESSION) do |gz|
                    out = Archive::Tar::Minitar::Output.new(gz)
                    Dir.chdir("#{fetch(:resource_path)}")

                    files = FileList['**/*']
                    exclude_list = fetch(:copy_exclude) + ['.git']

                    files.exclude(exclude_list).each do |file|
                        Archive::Tar::Minitar::pack_file(file, out)
                    end
                    out.close
                end
                info 'Finished'
                set :tarball_hash_value, Digest::MD5.hexdigest(File.open("#{fetch(:tmp_tarball)}/release-#{fetch(:project_release_id)}.tar.gz").read)
                info "created tarball's hash: #{fetch(:tarball_hash_value)}"
            else
                info 'Alrady updated.'
            end
        end
    end

同じものがS3にない場合、tarを固めます。

lib/capistrano/tarball_deploy.rb
#(省略)
    def upload_tarball_to_s3(upload_file)
        Aws.config[:credentials] = Aws::SharedCredentials.new(
            path: fetch(:aws_credential_file),
            profile_name: fetch(:aws_profile_name)
        )
        Aws.config[:region] = fetch(:region)
        s3 = Aws::S3::Client.new()
        s3.put_object(
            bucket: fetch(:bucket_name),
            body: File.open(upload_file),
            key: fetch(:s3_path) + File.basename(upload_file)
        )
    end

    def check_aws_configuration()
    #(省略)
end

##Jenkins設定
###パラメーター設定
以下のようにすべて環境変数で渡したものをパラメーター化します。
스크린샷 2015-12-22 19.51.46.png

###シェルスクリプト

cd ${WORKSPACE}/capistrano
SERVERS=${SERVER} /usr/local/bin/cap production_pull deploy DEPLOY_TYPE=${DEPLOY_TYPE} WEB_SERVER_SSH=${WEB_SERVER_SSH} WEB_SERVER_USER=${WEB_SERVER_USER} S3_PATH=${S3_PATH} REGION=${REGION} BUCKET_NAME=${BUCKET_NAME} RESOURCE_PATH=${RESOURCE_PATH} AWS_PROFILE_NAME=${AWS_PROFILE_NAME} AWS_CREDENTIAL_FILE=${AWS_CREDENTIAL_FILE} DEPLOY_TO=${DEPLOY_TO}

##起きたエラー

エラー部分:sudo: sorry,you must have a tty to run sudo.

参考:http://qiita.com/e2_adachi/items/7abf974d81067bd2b746
set :pty, trueを書くことで解決

The deploy has failed with an error: Exception while executing as xx@xxxxx: sudo /etc/init.d/httpd
      graceful    exit status: 1
      sudo /etc/init.d/httpd graceful stdout: [sudo] password for xx:
      sudo /etc/init.d/httpd graceful stderr: Nothing written

参考:http://blog.suz-lab.com/2011/01/sudoetcinitdhttpd.html
http://yut.hatenablog.com/entry/20111013/1318436872

/tmp/hudson4186181752264859077.sh: line 3: cap: command not found

参考:http://itpro.nikkeibp.co.jp/article/COLUMN/20060228/230996/
which capで確認したところ、capではなく、/usr/local/bin/capでした。

The deploy has failed with an error: Exception while executing as xx@xxxxxx: getaddrinfo: nodename nor servname provided, or not known

参考:http://www.linux-beginner.com/linux_setei1.html
IPアドレスとそれに対応するホスト名の設定ができてない。

最後に

今回Capistranoのことを書きましたが、いかがでしょうか。誰かの参考になれば幸せです。

Capistranoを知ったきっかけは先輩から社内で使っているCapistranoの改善のことで実行テスト・ソースコード修正をお願いされたことでした。Capistranoを理解やエラーが出ると詰まったり、先輩からお願いされたソースコードの内容が理解できなくって色々大変でしたが、いい経験になりました!

残念ながらCYBIRDエンジニア Advent Calendarは以上となります。
来年がお楽しみです!

Merry Christmas!!!

11
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?