Created
January 4, 2022 11:26
-
-
Save plugine/8a96f6d5bc0b434e1d757f799a4bde4e to your computer and use it in GitHub Desktop.
docker build script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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