Skip to content

Instantly share code, notes, and snippets.

@plugine
Created January 4, 2022 11:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save plugine/8a96f6d5bc0b434e1d757f799a4bde4e to your computer and use it in GitHub Desktop.
Save plugine/8a96f6d5bc0b434e1d757f799a4bde4e to your computer and use it in GitHub Desktop.
docker build script
require 'json'
require 'http'
APP_NAME = ENV['APP_NAME']
DEPLOY_COUNT = ENV['DEPLOY_COUNT']
INNER_PORT = ENV['INNER_PORT']
EXPORT_PORT_START = ENV['EXPORT_PORT_START']
NGINX_CONFIG_PATH = '/etc/nginx/sites/'
class Container
attr_accessor :image, :container_id, :port
end
# return running containers with name and container id
def current_running
`docker ps --format='{{json .}}'`.split("\n").map do |j|
m = JSON.parse(j)
next unless m['Names'].include?(APP_NAME)
Container.new.tap do |r|
r.image, r.port = m['Names'].gsub(APP_NAME, '').split('.').map(&:to_i)
r.container_id = m['ID']
end
end.compact.sort_by(&:port)
end
def port_occupied?(containers, port)
containers.each do |c|
return true if c.port == port
end
false
end
def start_container(container)
`docker run --publish=#{container.port}:#{INNER_PORT} --detach=true --name=#{APP_NAME}#{container.image}.#{container.port} --restart=always #{APP_NAME}:#{container.image}`
puts "start container on port #{container.port}"
sleep 2
end
def health_check(container, retry_time=5)
res = HTTP.get("http://localhost:#{container.port}/health").to_s
if res != 'ok'
puts "http not health, abort deployment, please check configurations"
exit(1)
end
rescue
if retry_time > 0
sleep 2
puts "retry health check"
health_check(container, retry_time - 1)
else
puts "can't connect http health check on container #{container.container_id}|#{container.port} after 5 times try, abort deployment"
exit(1)
end
end
def stop_container(container)
return if container.nil?
`docker container stop #{container.container_id}`
# remove it to make sure no duplicate name when rolling back
`docker container rm #{container.container_id}`
puts "stop container #{container.container_id} on port #{container.port}"
end
def upstream_conf(containers)
header = "upstream #{APP_NAME}_server {"
body = containers.map do |c|
" server 127.0.0.1:#{c.port};"
end
footer = "}"
[header, body, footer].flatten.join("\n")
end
def nginx_conf_file
NGINX_CONFIG_PATH + "#{APP_NAME}.conf"
end
def update_nginx_upstream(containers)
new_content = File.read(nginx_conf_file).gsub!(/upstream #{APP_NAME}_server {.*?}/m, upstream_conf(containers))
File.write(nginx_conf_file, new_content)
`nginx -s reload`
end
def rolling_update(image)
pendings = []
runnings = current_running
(EXPORT_PORT_START.to_i ... (EXPORT_PORT_START.to_i+100)).each do |port|
if !port_occupied?(runnings, port)
break if pendings.length >= DEPLOY_COUNT.to_i
container = Container.new.tap do |c|
c.image = image
c.port = port
end
pendings << container
start_container(container)
health_check(container)
need_remove = runnings.shift
update_nginx_upstream(pendings + runnings)
stop_container(need_remove)
end
end
while !(r = runnings.shift).nil?
update_nginx_upstream(pendings + runnings)
stop_container(r)
end
end
def usage
puts "Usage: rolling-update.rb command ...args"
puts " upgrade image: rolling-update.rb upgrade [image_tag_version]"
puts " get rollback version list: rolling-update.rb rollback list"
puts " rollback to specified version: rolling-update.rb rollback [image_tag_version]"
exit
end
def main(args)
usage if args.length == 0
case args[0]
when 'upgrade'
usage if args.length == 1
rolling_update(args[1])
when 'rollback'
usage if args.length == 1
if args[1] == 'list'
puts `docker images #{APP_NAME} | grep #{APP_NAME} | awk '{print $2}' | sort -r`
exit
end
rolling_update(args[1])
else
usage
end
end
def check_env_by_name!(name)
if !ENV[name]
puts "please set #{name} env"
exit(1)
end
end
def check_env!
%w(APP_NAME DEPLOY_COUNT INNER_PORT EXPORT_PORT_START).each do |name|
check_env_by_name!(name)
end
end
def check_nginx_conf_path!
if !File.exist?(nginx_conf_file)
puts "nginx conf file not exist, please check #{nginx_conf_file}"
exit(1)
end
end
check_env!
check_nginx_conf_path!
main(ARGV)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment