Capistranoでdocker-compose.ymlをデプロイする
普段dockerをデプロイするときには、ecrにpushしたり最近流行りのRancherだとcliを叩いたり、
それをcircleciに頼んだりしていたのですが、
今回は訳あってcapistranoを使うことになったので、そのことについて書きます
capistranoとは
- Capistrano 3はRubyをベースにしたサーバ操作およびデプロイの自動化ツールです。Capistrano 3を利用することで、デプロイなどの複雑なサーバ操作を自動化することができます。
- 要するにコマンド1発でデプロイできます
- capistranoの基本的な設定はこちらなどを参考にしてください
やっていること
- 開いているポートを探す
- 新しいコンテナを立ち上げる
- 古いコンテナを止める
- 古いコンテナを削除する
- もしも途中で失敗したら、ロールバックして元に戻す
構成
- 今回は、7777ポートと7778ポートを交互にデプロイします。その際HAProxyでcheckしているので、ほぼ無停止でデプロイできます。
それでは実際のコードですが
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もうすこし勉強したいです