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
|
* ✅ Fix proxy reload / Make it reload on file changes
|
||||||
* Implement `faaso help command`
|
* Implement `faaso help command`
|
||||||
* Fix `export examples/hello_crystal` it has a `template/`
|
* 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
|
* Cleanup `tmp/` after use unless `DEBUG` is set
|
||||||
|
|
||||||
# Things to do but not before release
|
# 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/build.cr"
|
||||||
|
require "./commands/deploy.cr"
|
||||||
require "./commands/export.cr"
|
require "./commands/export.cr"
|
||||||
require "./commands/login.cr"
|
require "./commands/login.cr"
|
||||||
require "./commands/new.cr"
|
require "./commands/new.cr"
|
||||||
|
35
src/funko.cr
35
src/funko.cr
@ -85,16 +85,18 @@ module Funko
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Set the number of running instances of this funko
|
# 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)
|
docker_api = Docr::API.new(Docr::Client.new)
|
||||||
current_scale = self.scale
|
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}" }
|
Log.info { "Scaling #{name} from #{current_scale} to #{new_scale}" }
|
||||||
if new_scale > current_scale
|
if new_scale > current_scale
|
||||||
(current_scale...new_scale).each {
|
(current_scale...new_scale).each {
|
||||||
Log.info { "Adding instance" }
|
Log.info { "Adding instance" }
|
||||||
id = create_container
|
result << (id = create_container)
|
||||||
start(id)
|
start(id)
|
||||||
sleep 0.1.seconds
|
sleep 0.1.seconds
|
||||||
}
|
}
|
||||||
@ -105,6 +107,7 @@ module Funko
|
|||||||
}.each { |container|
|
}.each { |container|
|
||||||
Log.info { "Removing instance" }
|
Log.info { "Removing instance" }
|
||||||
docker_api.containers.stop(container.@id)
|
docker_api.containers.stop(container.@id)
|
||||||
|
result << container.@id
|
||||||
current_scale -= 1
|
current_scale -= 1
|
||||||
break if current_scale == new_scale
|
break if current_scale == new_scale
|
||||||
sleep 0.1.seconds
|
sleep 0.1.seconds
|
||||||
@ -116,6 +119,8 @@ module Funko
|
|||||||
Log.info { "Pruning dead instance" }
|
Log.info { "Pruning dead instance" }
|
||||||
docker_api.containers.delete(container.@id)
|
docker_api.containers.delete(container.@id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
# Setup the target directory `path` with all the files needed
|
# Setup the target directory `path` with all the files needed
|
||||||
@ -184,7 +189,7 @@ module Funko
|
|||||||
docker_api.containers.list(all: true).select { |container|
|
docker_api.containers.list(all: true).select { |container|
|
||||||
container.@names.any?(&.starts_with?("/faaso-#{name}-")) &&
|
container.@names.any?(&.starts_with?("/faaso-#{name}-")) &&
|
||||||
container.@state == "running"
|
container.@state == "running"
|
||||||
}
|
} || [] of Docr::Types::ContainerSummary
|
||||||
end
|
end
|
||||||
|
|
||||||
# A comprehensive status for the funko:
|
# A comprehensive status for the funko:
|
||||||
@ -207,12 +212,26 @@ module Funko
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Wait up to `t` seconds for the funko to reach the requested `state`
|
# Wait up to `t` seconds for the funko to reach the desired scale
|
||||||
def wait_for(new_scale : Int, t)
|
# 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
|
channel = Channel(Nil).new
|
||||||
spawn do
|
spawn do
|
||||||
loop do
|
loop do
|
||||||
channel.send(nil) if scale == new_scale
|
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
|
sleep 0.2.seconds
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -221,7 +240,7 @@ module Funko
|
|||||||
when channel.receive
|
when channel.receive
|
||||||
Log.info { "Funko #{name} reached scale #{new_scale}" }
|
Log.info { "Funko #{name} reached scale #{new_scale}" }
|
||||||
when timeout(t.seconds)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -42,6 +42,8 @@ status : Int32 = 0
|
|||||||
case ans
|
case ans
|
||||||
when .fetch("build", false)
|
when .fetch("build", false)
|
||||||
status = Faaso::Commands::Build.new.run(ans, ans["FOLDER"].as(Array(String)))
|
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)
|
when .fetch("export", false)
|
||||||
status = Faaso::Commands::Export.new.run(ans, ans["SOURCE"].as(String), ans["DESTINATION"].as(String))
|
status = Faaso::Commands::Export.new.run(ans, ans["SOURCE"].as(String), ans["DESTINATION"].as(String))
|
||||||
when .fetch("login", false)
|
when .fetch("login", false)
|
||||||
|
Loading…
Reference in New Issue
Block a user