Edited at

Capistranoでdocker-compose.ymlをデプロイする

More than 1 year has passed since last update.


Capistranoでdocker-compose.ymlをデプロイする

普段dockerをデプロイするときには、ecrにpushしたり最近流行りのRancherだとcliを叩いたり、

それをcircleciに頼んだりしていたのですが、

今回は訳あってcapistranoを使うことになったので、そのことについて書きます


capistranoとは


  • Capistrano 3はRubyをベースにしたサーバ操作およびデプロイの自動化ツールです。Capistrano 3を利用することで、デプロイなどの複雑なサーバ操作を自動化することができます。

  • 要するにコマンド1発でデプロイできます

  • capistranoの基本的な設定はこちらなどを参考にしてください


やっていること


  1. 開いているポートを探す

  2. 新しいコンテナを立ち上げる

  3. 古いコンテナを止める

  4. 古いコンテナを削除する


  • もしも途中で失敗したら、ロールバックして元に戻す


構成


  • 今回は、7777ポートと7778ポートを交互にデプロイします。その際HAProxyでcheckしているので、ほぼ無停止でデプロイできます。

Untitled.001.jpeg


それでは実際のコードですが


docker-compose.yml


  • CAP_DOCKER_COMPOSE_PORTでdocker-composeのポートを指定しています


docker-compose.yml

version: '2'

services:
nginx:
build: ./nginx
ports:
- "${CAP_DOCKER_COMPOSE_PORT}:80"
command: sh -c "nginx -g 'daemon off;'"
volumes:
- run:/var/run/
restart: always
logging:
driver: "awslogs"
options:
awslogs-region: "ap-northeast-1"
awslogs-group: "${NODE_ENV}"
awslogs-stream: "nginx"
node:
build: ./node
volumes:
- run:/var/run/
command: sh -c "sh run.sh"
restart: always
logging:
driver: "awslogs"
options:
awslogs-region: "ap-northeast-1"
awslogs-group: "${NODE_ENV}"
awslogs-stream: "node"
volumes:
run:



deploy.rb


config/deploy.rb

set :application, 'app-name' 

set :repo_url, 'git@github.com:hoghoghoho.git'
set :branch, 'master'

set :containers_log, deploy_path + 'containers.log'
set :pty, false
set :linked_dirs, %w{log}

set :docker_compose_port_range, 7777..7778

set :keep_releases, 3

set :ssh_options, {
keys: [File.expand_path('./ssh/id_rsa')],
forward_agent: true,
auth_methods: %w(publickey)
}

Rake::Task['metrics:collect'].clear_actions

namespace :deploy do
## deploy
task :before_path_setting do
on roles(fetch(:docker_compose_roles)) do
set :rollback_path, previous_release
end
end
# コンテナをスタートさせます
task :start_containers do
on roles(fetch(:docker_compose_roles)) do
set :previous_release_path, previous_release
within release_path do
with cap_docker_compose_root_path: fetch(:deploy_to), cap_docker_compose_port: fetch(:release_port) do
execute :'docker-compose', '-f', "docker-compose.yml", 'up','-d'
sleep 3
end
end
end
end

# 古いコンテナを削除します
task :purge_old_containers do
on roles(fetch(:docker_compose_roles)) do
if fetch(:previous_release_path, false)
within fetch(:previous_release_path) do
info "Purging containers of previous release at #{fetch(:previous_release_path)}"
with cap_docker_compose_port: fetch(:release_port) do
execute :'docker-compose', 'stop'
end
end
end
end
end
# 途中でデプロイに失敗したら、新しいコンテナを破棄して、元に戻します
task :purge_failed_containers do
set :cap_docker_compose_failed, true
on roles(fetch(:docker_compose_roles)) do
if fetch(:previous_release_path, false)
within release_path do
with cap_docker_compose_port: fetch(:release_port) do
info "Purging failed containers at #{release_path}"
execute :'docker-compose', 'down'
end
end
end
end
end

# 古いコンテナをstopします
task :stop_previous_release do
on roles(fetch(:docker_compose_roles)) do
set :previous_release_path, previous_release
if fetch(:previous_release_path, false)
within fetch(:previous_release_path) do
containers = capture :'docker', 'ps', '-q'
unless containers.empty?
info "Stopping containers of previous release"
execute :"export CAP_DOCKER_COMPOSE_PORT=#{fetch(:release_port)} && cd #{previous_release} && docker-compose down"
end
end
end
end
end

# 空いているポートを探す
def detect_available_port
ports = fetch(:docker_compose_port_range)
ports.each do |port|
port_response = capture("netstat -lnt | awk '$6 == \"LISTEN\" && $4 ~ \".#{port}\"'")
if port_response.empty?
info "Port #{port} of #{ports.to_s} is free"
return port
end
end
raise "No port available in range #{ports.to_s}. Deployment aborted."
end

def previous_release
path = "#{fetch(:deploy_to)}/current"
if test("[ -L #{path} ]")
return capture("readlink -f #{path}")
end
return nil
end

#==================================================
# rollback
#==================================================

task :start_rollback_containers do
on roles(fetch(:docker_compose_roles)) do
set :previous_release_path, previous_release
set :release_port, detect_available_port
within release_path do
with cap_docker_compose_root_path: fetch(:deploy_to), cap_docker_compose_port: fetch(:release_port) do
execute :"export CAP_DOCKER_COMPOSE_PORT=#{fetch(:release_port)}&& cd #{previous_release} && RAILS_ENV=#{fetch(:rails_env)} docker-compose -f docker-compose.yml up -d"
sleep 3
end
end
end
end

task :stop_current_release do
on roles(fetch(:docker_compose_roles)) do
if fetch(:rollback_path, false)
within fetch(:rollback_path) do
info "Stopping containers of previous release"
execute :"export CAP_DOCKER_COMPOSE_PORT=8888#{fetch(:release_port)} && cd #{previous_release} && docker-compose down"
end
end
end
end

desc 'reset task'
task :reset do
on roles(fetch(:docker_compose_roles)) do
containers = capture :'docker', 'ps', '-q'
unless containers.empty?
execute :'docker stop `docker ps -q`'
execute :'docker rm `docker ps -q -a`'
#execute :'docker network ls -q | xargs docker network rm'
end
invoke 'deploy'
end
end

after :updating, :start_containers
before :updated, :stop_previous_release
after :failed, :purge_failed_containers
after :failed, :cleanup_rollback
after :finished, :purge_old_containers unless fetch(:cap_docker_compose_failed, false)

after :rollback, :start_rollback_containers
before :reverting, :stop_current_release
before :starting, :before_path_setting

end

namespace :load do
task :defaults do
set :docker_compose_roles, fetch(:docker_compose_roles, :all)
end
end



haproxy.cfg


haproxy.cfg

global

log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
user haproxy
group haproxy
daemon

defaults
log global
mode http
option httplog
option dontlognull
option http-server-close
option redispatch
timeout connect 5000
timeout http-keep-alive 15s
timeout server 50000
timeout client 50000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http

lesten server
bind *:3000
mode tcp

balance roundrobin
option forwardfor
option httpchk get /
http-check expect ! rstatus ^5
default-server fall 1 rise 1
server container1 127.0.0.1:7777 observe layer7
server container2 127.0.0.1:7778 observe layer7



  • bind *:3000ポートで待ち受けています


  • observe layer7 この部分で生存チェックをしています


デプロイする

$ bundle exec cap production deploy

- これでしばらく待っていると、デプロイされます

- 試しに、serverでdocker psなど打つと良いでしょう


まとめ


  • ロールバックの実装が大変だったです

  • haproxyもうすこし勉強したいです