Faaso deploy command
This commit is contained in:
parent
5e7764ca9f
commit
8e6ec620aa
2
TODO.md
2
TODO.md
@ -31,7 +31,7 @@
|
||||
* ✅ Fix proxy reload / Make it reload on file changes
|
||||
* Implement `faaso help command`
|
||||
* Fix `export examples/hello_crystal` it has a `template/`
|
||||
* Implement zero-downtime rollout (`faaso deploy`)
|
||||
* ✅ Implement zero-downtime rollout (`faaso deploy`)
|
||||
* Cleanup `tmp/` after use unless `DEBUG` is set
|
||||
|
||||
# Things to do but not before release
|
||||
|
41
src/commands/deploy.cr
Normal file
41
src/commands/deploy.cr
Normal file
@ -0,0 +1,41 @@
|
||||
module Faaso
|
||||
module Commands
|
||||
struct Deploy
|
||||
# FIXME: local only for now
|
||||
def run(options, funko_name : String) : Int32
|
||||
Log.info { "Deploying #{funko_name}" }
|
||||
funko = Funko::Funko.from_names([funko_name])[0]
|
||||
# Get scale, check for out-of-date containers
|
||||
current_scale = funko.scale
|
||||
latest_image = funko.latest_image
|
||||
containers = funko.containers
|
||||
out_of_date = containers.count { |container| container.image_id != latest_image }
|
||||
Log.info { "Need to update #{out_of_date} containers" }
|
||||
Log.info { "Scaling from #{current_scale} to #{current_scale + out_of_date}" }
|
||||
# Increase scale to get enough up-to-date containers
|
||||
new_containers = funko.scale(current_scale + out_of_date)
|
||||
|
||||
# Wait for them to be healthy
|
||||
begin
|
||||
funko.wait_for(current_scale + out_of_date, 120, healthy: true)
|
||||
rescue ex : Exception
|
||||
# Failed to start, rollback
|
||||
Log.error(exception: ex) { "Failed to scale, rolling back" }
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
new_containers.each do |container|
|
||||
docker_api.containers.stop(container.id)
|
||||
docker_api.containers.delete(container.id)
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
Log.info { "Scaling down to #{current_scale}" }
|
||||
# Decrease scale to the desired amount
|
||||
funko.scale(current_scale)
|
||||
funko.wait_for(current_scale, 30)
|
||||
Log.info { "Deployed #{funko_name}" }
|
||||
0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,4 +1,5 @@
|
||||
require "./commands/build.cr"
|
||||
require "./commands/deploy.cr"
|
||||
require "./commands/export.cr"
|
||||
require "./commands/login.cr"
|
||||
require "./commands/new.cr"
|
||||
|
33
src/funko.cr
33
src/funko.cr
@ -85,16 +85,18 @@ module Funko
|
||||
end
|
||||
|
||||
# Set the number of running instances of this funko
|
||||
def scale(new_scale : Int)
|
||||
# Returns the list of IDs started or stopped
|
||||
def scale(new_scale : Int) : Array(String)
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
current_scale = self.scale
|
||||
return if current_scale == new_scale
|
||||
result = [] of String
|
||||
return result if current_scale == new_scale
|
||||
|
||||
Log.info { "Scaling #{name} from #{current_scale} to #{new_scale}" }
|
||||
if new_scale > current_scale
|
||||
(current_scale...new_scale).each {
|
||||
Log.info { "Adding instance" }
|
||||
id = create_container
|
||||
result << (id = create_container)
|
||||
start(id)
|
||||
sleep 0.1.seconds
|
||||
}
|
||||
@ -105,6 +107,7 @@ module Funko
|
||||
}.each { |container|
|
||||
Log.info { "Removing instance" }
|
||||
docker_api.containers.stop(container.@id)
|
||||
result << container.@id
|
||||
current_scale -= 1
|
||||
break if current_scale == new_scale
|
||||
sleep 0.1.seconds
|
||||
@ -116,6 +119,8 @@ module Funko
|
||||
Log.info { "Pruning dead instance" }
|
||||
docker_api.containers.delete(container.@id)
|
||||
}
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
# Setup the target directory `path` with all the files needed
|
||||
@ -184,7 +189,7 @@ module Funko
|
||||
docker_api.containers.list(all: true).select { |container|
|
||||
container.@names.any?(&.starts_with?("/faaso-#{name}-")) &&
|
||||
container.@state == "running"
|
||||
}
|
||||
} || [] of Docr::Types::ContainerSummary
|
||||
end
|
||||
|
||||
# A comprehensive status for the funko:
|
||||
@ -207,12 +212,26 @@ module Funko
|
||||
end
|
||||
end
|
||||
|
||||
# Wait up to `t` seconds for the funko to reach the requested `state`
|
||||
def wait_for(new_scale : Int, t)
|
||||
# Wait up to `t` seconds for the funko to reach the desired scale
|
||||
# If `healthy` is true, it will wait for the container to be declared
|
||||
# healthy by the healthcheck
|
||||
def wait_for(new_scale : Int, t : Int, healthy : Bool = false)
|
||||
docker_api = Docr::API.new(Docr::Client.new)
|
||||
channel = Channel(Nil).new
|
||||
spawn do
|
||||
loop do
|
||||
if healthy
|
||||
channel.send(nil) if containers.count { |container|
|
||||
begin
|
||||
container = docker_api.containers.inspect(container.@id)
|
||||
channel.send(nil) if !container.nil? && (container.state.health.status == "healthy")
|
||||
rescue ex : Docr::Errors::DockerAPIError
|
||||
Log.error { "#{ex}" } unless ex.status_code == 304 # This just happens
|
||||
end
|
||||
} == new_scale
|
||||
else
|
||||
channel.send(nil) if scale == new_scale
|
||||
end
|
||||
sleep 0.2.seconds
|
||||
end
|
||||
end
|
||||
@ -221,7 +240,7 @@ module Funko
|
||||
when channel.receive
|
||||
Log.info { "Funko #{name} reached scale #{new_scale}" }
|
||||
when timeout(t.seconds)
|
||||
Log.error { "Funko #{name} did not reach scale #{new_scale} in #{t} seconds" }
|
||||
raise Exception.new("Funko #{name} did not reach scale #{new_scale} in #{t} seconds")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -42,6 +42,8 @@ status : Int32 = 0
|
||||
case ans
|
||||
when .fetch("build", false)
|
||||
status = Faaso::Commands::Build.new.run(ans, ans["FOLDER"].as(Array(String)))
|
||||
when .fetch("deploy", false)
|
||||
status = Faaso::Commands::Deploy.new.run(ans, ans["FUNKO"].as(String))
|
||||
when .fetch("export", false)
|
||||
status = Faaso::Commands::Export.new.run(ans, ans["SOURCE"].as(String), ans["DESTINATION"].as(String))
|
||||
when .fetch("login", false)
|
||||
|
Loading…
Reference in New Issue
Block a user