mkit 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +21 -0
  3. data/Gemfile.lock +137 -0
  4. data/LICENSE +21 -0
  5. data/README.md +126 -0
  6. data/Rakefile +54 -0
  7. data/bin/mkitc +31 -0
  8. data/bin/mkitd +55 -0
  9. data/config/database.yml +5 -0
  10. data/config/mkit_config.yml +15 -0
  11. data/config/mkitd_config.sh +5 -0
  12. data/db/migrate/001_setup.rb +105 -0
  13. data/db/migrate/002_mkit_jobs.rb +17 -0
  14. data/db/schema.rb +108 -0
  15. data/lib/mkit/app/controllers/mkitjobs_controller.rb +37 -0
  16. data/lib/mkit/app/controllers/pods_controller.rb +30 -0
  17. data/lib/mkit/app/controllers/services_controller.rb +87 -0
  18. data/lib/mkit/app/helpers/docker_helper.rb +75 -0
  19. data/lib/mkit/app/helpers/erb_helper.rb +18 -0
  20. data/lib/mkit/app/helpers/haproxy.rb +41 -0
  21. data/lib/mkit/app/helpers/interface_helper.rb +17 -0
  22. data/lib/mkit/app/helpers/services_helper.rb +54 -0
  23. data/lib/mkit/app/mkit_server.rb +8 -0
  24. data/lib/mkit/app/model/dns_host.rb +11 -0
  25. data/lib/mkit/app/model/lease.rb +26 -0
  26. data/lib/mkit/app/model/mkit_job.rb +48 -0
  27. data/lib/mkit/app/model/pod.rb +95 -0
  28. data/lib/mkit/app/model/pool.rb +60 -0
  29. data/lib/mkit/app/model/service.rb +266 -0
  30. data/lib/mkit/app/model/service_config.rb +16 -0
  31. data/lib/mkit/app/model/service_port.rb +30 -0
  32. data/lib/mkit/app/model/setting.rb +1 -0
  33. data/lib/mkit/app/model/volume.rb +53 -0
  34. data/lib/mkit/app/templates/docker/docker_run.sh.erb +1 -0
  35. data/lib/mkit/app/templates/haproxy/0000_defaults.cfg +23 -0
  36. data/lib/mkit/app/templates/haproxy/xapp_haproxy.cfg.erb +30 -0
  37. data/lib/mkit/cmd_runner.rb +27 -0
  38. data/lib/mkit/config/config.rb +18 -0
  39. data/lib/mkit/config/environment.rb +26 -0
  40. data/lib/mkit/config/initializers/001_hash.rb +11 -0
  41. data/lib/mkit/config/initializers/002_openstruct.rb +7 -0
  42. data/lib/mkit/config/load_default_configs.rb +29 -0
  43. data/lib/mkit/config/the_config.yml +3 -0
  44. data/lib/mkit/ctypes.rb +31 -0
  45. data/lib/mkit/docker_listener.rb +97 -0
  46. data/lib/mkit/exceptions.rb +30 -0
  47. data/lib/mkit/haproxy.rb +48 -0
  48. data/lib/mkit/job_manager.rb +53 -0
  49. data/lib/mkit/mkit_dns.rb +54 -0
  50. data/lib/mkit/mkit_interface.rb +31 -0
  51. data/lib/mkit/sagas/asaga.rb +11 -0
  52. data/lib/mkit/sagas/create_pod_saga.rb +28 -0
  53. data/lib/mkit/sagas/saga_manager.rb +10 -0
  54. data/lib/mkit/status.rb +47 -0
  55. data/lib/mkit/utils.rb +51 -0
  56. data/lib/mkit/version.rb +4 -0
  57. data/lib/mkit/workers/aworker.rb +11 -0
  58. data/lib/mkit/workers/haproxy_worker.rb +35 -0
  59. data/lib/mkit/workers/pod_worker.rb +39 -0
  60. data/lib/mkit/workers/service_worker.rb +27 -0
  61. data/lib/mkit/workers/worker_manager.rb +14 -0
  62. data/lib/mkit.rb +158 -0
  63. data/mkit.gemspec +40 -0
  64. data/mkitd +10 -0
  65. data/samples/apps/postgres.yml +22 -0
  66. data/samples/apps/rabbitmq.yml +19 -0
  67. data/samples/daemontools/log/run +44 -0
  68. data/samples/daemontools/run +42 -0
  69. data/samples/systemd/mkitd.service +12 -0
  70. metadata +393 -0
@@ -0,0 +1,97 @@
1
+ require 'pty'
2
+ require 'mkit/status'
3
+
4
+ #
5
+ # https://docs.docker.com/engine/reference/commandline/events
6
+ require 'mkit/app/helpers/docker_helper'
7
+ module MKIt
8
+ class DockerListener
9
+ include MKIt::DockerHelper
10
+
11
+ def initialize
12
+ @consumers = []
13
+ end
14
+
15
+ def register_consumer(consumer:)
16
+ end
17
+
18
+ def parse_message(msg)
19
+ action = msg['Action'].to_sym
20
+ type = msg['Type'].to_sym
21
+ MKItLogger.info("docker <#{type}> <#{action}> received: \n\t#{msg}")
22
+ case type
23
+ when :container
24
+ pod_id = msg.id
25
+ pod_name = msg.Actor.Attributes.name
26
+ pod = Pod.find_by(name: pod_name)
27
+ unless pod.nil?
28
+ case action
29
+ when :create
30
+ pod.pod_id = pod_id
31
+ pod.status = MKIt::Status::CREATED
32
+ pod.save
33
+ pod.service.update_status!
34
+ when :start
35
+ pod.pod_id = pod_id
36
+ pod.save
37
+ pod.service.update_status!
38
+ when :kill
39
+ MKItLogger.debug(" #{type} #{action} <<NOOP / TODO>>")
40
+ when :die
41
+ MKItLogger.debug(" #{type} #{action} <<NOOP / TODO>>")
42
+ when :stop
43
+ pod.service.update_status!
44
+ else
45
+ MKItLogger.debug(" #{type} #{action} <<TODO>>")
46
+ end
47
+ else
48
+ MKItLogger.warn("docker <<#{type}>> <#{action}> received: #{msg}. But I don't know anything about pod #{pod_id}")
49
+ end
50
+ when :network
51
+ pod_id = msg.Actor.Attributes.container
52
+ pod = Pod.find_by(pod_id: pod_id)
53
+ unless pod.nil?
54
+ case action
55
+ when :connect
56
+ MKItLogger.info("docker network #{action} received: #{msg}")
57
+ pod.update_ip
58
+ pod.save
59
+ when :disconnect
60
+ MKItLogger.debug(" #{type} #{action} <<NOOP / TODO>>")
61
+ else
62
+ MKItLogger.debug(" #{type} #{action} <<TODO>>")
63
+ end
64
+ else
65
+ MKItLogger.warn("docker <<#{type}>> <#{action}> received: #{msg}. But I don't know anything about pod #{pod_id}")
66
+ end
67
+ else
68
+ MKItLogger.info("\t#{type} #{action} <<unknown>>")
69
+ end
70
+ end
71
+
72
+ def start
73
+ @thread ||= Thread.new {
74
+ cmd = "docker events --format '{{json .}}'"
75
+ begin
76
+ PTY.spawn( cmd ) do |stdout, stdin, pid|
77
+ begin
78
+ stdout.each { |line| parse_message JSON.parse(line).to_o }
79
+ rescue Errno::EIO
80
+ MKItLogger.warn("Errno:EIO error, but this probably just means " +
81
+ "that the process has finished giving output")
82
+ end
83
+ end
84
+ rescue PTY::ChildExited
85
+ MKItLogger.warn("docker event listener process exited!")
86
+ end
87
+ }
88
+ @thread.run
89
+ MKItLogger.info("docker listener started")
90
+ end
91
+ def stop
92
+ @thread.exit if @thread
93
+ MKItLogger.info("docker listener stopped")
94
+ end
95
+ end
96
+ end
97
+
@@ -0,0 +1,30 @@
1
+ module MKIt
2
+ class BaseException < Exception
3
+ attr_reader :error_code
4
+ def initialize(error_code, message = nil)
5
+ super(message)
6
+ @error_code = error_code
7
+ end
8
+ end
9
+
10
+ class ServiceAlreadyExists < BaseException
11
+ def initialize(message = nil)
12
+ super(409, message)
13
+ end
14
+ end
15
+ class ServiceNameMismatch < BaseException
16
+ def initialize(message = nil)
17
+ super(400, message)
18
+ end
19
+ end
20
+ class ServiceNotFoundException < StandardError; end
21
+ class PodNotFoundException < StandardError; end
22
+ class AppAlreadyDeployedException < StandardError; end
23
+ class InvalidPortMappingTypeException < StandardError; end
24
+
25
+ class PoolExaustedException < StandardError; end
26
+
27
+ class CmdRunnerException < StandardError; end
28
+
29
+ end
30
+
@@ -0,0 +1,48 @@
1
+ require 'pty'
2
+ #
3
+ #
4
+ #
5
+ module MKIt
6
+ class HAProxy
7
+
8
+ def initialize
9
+ # configs
10
+ # run standalone | daemon
11
+ @running = false
12
+ end
13
+
14
+ def start
15
+ @thread ||= Thread.new {
16
+ while (@running) do
17
+ cmd = "/usr/sbin/haproxy -f /etc/haproxy/haproxy.d"
18
+ %x{#{cmd}}
19
+ sleep 1
20
+ end
21
+ }
22
+ @thread.run
23
+ puts "haproxy started"
24
+ end
25
+
26
+ def start
27
+ @running = true
28
+ @thread ||= Thread.new {
29
+ while (@running) do
30
+ %{/usr/sbin/haproxy -f /etc/haproxy/haproxy.d/}
31
+ sleep(1)
32
+ end
33
+ }
34
+ puts "proxy started"
35
+ end
36
+
37
+ def stop
38
+ puts "proxy stopped"
39
+ end
40
+
41
+ def status
42
+ end
43
+
44
+ def reload
45
+ end
46
+ end
47
+ end
48
+
@@ -0,0 +1,53 @@
1
+ require 'mkit/app/model/mkit_job'
2
+ require 'mkit/utils'
3
+
4
+ module MKIt
5
+ class JobManager
6
+ def initialize
7
+ @workers = {}
8
+ end
9
+
10
+ def register_worker(worker, topics)
11
+ topics.each { | topic |
12
+ @workers[topic] ||= []
13
+ MKItLogger.info("register #{worker.class} for topic #{topic}")
14
+ @workers[topic] << worker
15
+ }
16
+ end
17
+
18
+ def start
19
+ MKItLogger.info('starting job manager')
20
+ @thread = Thread.new do
21
+ loop do
22
+ job = MkitJob.take
23
+ begin
24
+ if job.nil?
25
+ sleep(10)
26
+ else
27
+ topic = job.topic
28
+ job.processing!
29
+ if @workers[topic].nil?
30
+ MKItLogger.warn("no workers found for topic '#{topic}'")
31
+ else
32
+ workers = @workers[topic]
33
+ workers.each { | worker |
34
+ worker.do_the(job)
35
+ }
36
+ end
37
+ end
38
+ job.done! unless job.nil?
39
+ rescue Exception => e
40
+ job.error! unless job.nil?
41
+ MKItLogger.error e, e.message, e.backtrace.join("\n")
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def stop
48
+ @thread.exit if @thread
49
+ end
50
+ end
51
+ end
52
+
53
+
@@ -0,0 +1,54 @@
1
+ require 'async/dns'
2
+ require 'async/dns/system'
3
+ require 'mkit/mkit_interface'
4
+ require 'ipaddr'
5
+
6
+ # INTERFACES = [
7
+ # [:udp, "127.0.0.20", 53],
8
+ # [:tcp, "127.0.0.20", 53],
9
+ # ]
10
+ # @resolver = RubyDNS::Resolver.new(
11
+ # [:udp, "192.168.4.254", 53],
12
+ # [:tcp, "192.168.4.254", 53]
13
+ # )
14
+ #
15
+ # # Use upstream DNS for name resolution.
16
+ UPSTREAM = RubyDNS::Resolver.new([
17
+ [:udp, "8.8.8.8", 53],
18
+ [:tcp, "8.8.8.8", 53]
19
+ ])
20
+
21
+ Name = Resolv::DNS::Name
22
+ IN = Resolv::DNS::Resource::IN
23
+
24
+ module MKIt
25
+ class DNS < Async::DNS::Server
26
+ def initialize
27
+ addr = MKIt::Interface.ip
28
+ listen_addr = [
29
+ [:udp, addr, 53],
30
+ [:tcp, addr, 53],
31
+ ]
32
+ super(listen_addr)
33
+ @logger.info!
34
+ @resolver = RubyDNS::Resolver.new(Async::DNS::System.nameservers)
35
+ end
36
+ def process(name, resource_class, transaction)
37
+ host = DnsHost.find_by_name(name)
38
+ if host.nil?
39
+ transaction.passthrough!(@resolver)
40
+ else
41
+ ipaddr = IPAddr.new host.ip
42
+ if resource_class == Resolv::DNS::Resource::IN::A
43
+ transaction.respond!(ipaddr.to_s)
44
+ elsif resource_class == Resolv::DNS::Resource::IN::AAAA
45
+ transaction.respond!(ipaddr.ipv4_mapped.to_s)
46
+ else
47
+ transaction.fail!(:NXDomain)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+
@@ -0,0 +1,31 @@
1
+ require 'mkit/status'
2
+ require 'mkit/utils'
3
+ require 'mkit/exceptions'
4
+ require 'mkit/app/helpers/interface_helper'
5
+
6
+ module MKIt
7
+ class Interface
8
+ def self.ip
9
+ main_pool = Pool.find_by_name(MKIt::Utils.me)
10
+ main_pool.ip
11
+ end
12
+
13
+ def self.up
14
+ main_pool = Pool.find_by_name(MKIt::Utils.me)
15
+ interface_name = "#{main_pool.name}0"
16
+ interface_type = "tap"
17
+ ip = main_pool.ip
18
+ mask = main_pool.netmask
19
+ MKIt::InterfaceHelper.create(name: interface_name, ctype: interface_type)
20
+ MKIt::InterfaceHelper.up(name: interface_name, ip: ip, mask: mask)
21
+ end
22
+
23
+ def self.down
24
+ main_pool = Pool.find_by_name(MKIt::Utils.me)
25
+ interface_name = "#{main_pool.name}0"
26
+ interface_type = "tap"
27
+ MKIt::InterfaceHelper.down(name: interface_name)
28
+ MKIt::InterfaceHelper.remove(name: interface_name, ctype: interface_type)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ require 'mkit/status'
2
+ module MKIt
3
+ class ASaga
4
+
5
+ # Must define topics method on subclass
6
+ def initialize
7
+ System[:job_manager].register_worker(self, self.topics)
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,28 @@
1
+ module MKIt
2
+ class CreatePodSaga < ASaga
3
+
4
+ def topics
5
+ %w{create_pod_saga}
6
+ end
7
+
8
+ #
9
+ # create_pod_saga:
10
+ #
11
+ # payload:
12
+ # * service_id
13
+ #
14
+ # triggers
15
+ # * nothing
16
+ #
17
+ def do_the(job)
18
+ MKItLogger.info("#{self.class} <#{job.topic}> #{job.inspect}....")
19
+ service = Service.find(job.service_id)
20
+ # create pod
21
+
22
+ pd = Pod.new( service: service, status: MKIt::Status::CREATED, name: SecureRandom.uuid.gsub('-','')[0..11])
23
+ service.pod << pd
24
+ service.save
25
+ MkitJob.publish(topic: :start_pod, service_id: job.service_id, pod_id: pd.id)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,10 @@
1
+ require 'mkit/sagas/asaga'
2
+ require 'mkit/sagas/create_pod_saga'
3
+
4
+ module MKIt
5
+ class SagaManager
6
+ def self.register_workers
7
+ CreatePodSaga.new
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,47 @@
1
+ #
2
+ #
3
+ #
4
+ module MKIt
5
+ class MKItStatus
6
+ def initialize(status)
7
+ @status = status
8
+ end
9
+ def to_s
10
+ @status.to_s
11
+ end
12
+ end
13
+
14
+ module Status
15
+ # APP
16
+ CREATED = 'CREATED'
17
+ CREATING = 'CREATING'
18
+ DEPLOYING = 'DEPLOYING'
19
+ DEPLOYED = 'DEPLOYED'
20
+ PENDING = 'PENDING'
21
+ DEGRATED = 'DEGRATED'
22
+
23
+ # network
24
+ RESERVED = 'RESERVED'
25
+ IN_USE = 'IN_USE'
26
+ EXPIRED = 'EXPIRED'
27
+
28
+ # pods
29
+ STARTING = 'STARTING'
30
+ RUNNING = 'RUNNING'
31
+ STOPPED = 'STOPPED'
32
+ STOPING = 'STOPING'
33
+ PAUSED = 'PAUSED'
34
+
35
+ # Service
36
+ RESTARTING = 'RESTARTING'
37
+ UPDATING = 'UPDATING'
38
+ end
39
+
40
+ module PoolStatus
41
+
42
+ RESERVED = 'RESERVED'
43
+ IN_USE = 'IN_USE'
44
+ EXPIRED = 'EXPIRED'
45
+ EXAUSTED = 'EXAUSTED'
46
+ end
47
+ end
data/lib/mkit/utils.rb ADDED
@@ -0,0 +1,51 @@
1
+ require 'erb'
2
+ require 'mkit/config/config'
3
+ module MKIt
4
+ module Utils
5
+ module_function
6
+
7
+ def me
8
+ 'mkit'
9
+ end
10
+
11
+ def log
12
+ Console.logger
13
+ end
14
+
15
+ def root
16
+ File.expand_path("../../..", __FILE__)
17
+ end
18
+
19
+ def set_config_dir(config_dir)
20
+ @config_dir = config_dir
21
+ end
22
+
23
+ def config_dir
24
+ @config_dir.nil? ? "#{self.root}/config" : @config_dir
25
+ end
26
+
27
+ def load_db_config(db_config_dir = self.config_dir)
28
+ self.log.info "loading database configurations from '#{config_dir}'..."
29
+ YAML::load(ERB.new(IO.read("#{db_config_dir}/database.yml")).result)
30
+ end
31
+
32
+ def db_config_to_uri(env = MKIt::Config.mkit.database.env)
33
+ config = self.load_db_config[env]
34
+
35
+ if config["username"] || config["password"]
36
+ user_info = [ config["username"], config["password"] ].join(":")
37
+ else
38
+ user_info = nil
39
+ end
40
+ URI::Generic.new(config["adapter"],user_info,
41
+ config["hostname"] || "localhost",
42
+ config["port"],
43
+ nil,
44
+ "/#{config["database"]}",
45
+ nil,
46
+ nil,
47
+ nil).to_s
48
+ end
49
+ end
50
+ end
51
+
@@ -0,0 +1,4 @@
1
+ module MKIt
2
+ VERSION = "0.2.0"
3
+ end
4
+
@@ -0,0 +1,11 @@
1
+ require 'mkit/status'
2
+ module MKIt
3
+ class AWorker
4
+
5
+ # Must define topics method on subclass
6
+ def initialize
7
+ System[:job_manager].register_worker(self, self.topics)
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,35 @@
1
+ module MKIt
2
+ class HAProxyWorker < AWorker
3
+
4
+ def topics
5
+ %w{create_proxy_config destroy_proxy_config update_proxy_config restart_proxy reload_proxy}
6
+ end
7
+
8
+ def do_the(job)
9
+ MKItLogger.info("#{self.class} working on the job #{job.inspect}....")
10
+ unless job.service_id.nil?
11
+ srv = Service.find(job.service_id)
12
+ config = srv.proxy_config
13
+ end
14
+ case job.topic.to_sym
15
+ when :update_proxy_config
16
+ MKItLogger.debug config.inspect
17
+ MKIt::HAProxy.create_config_file(filename: config[:filename], data: config[:data])
18
+ MKIt::HAProxy.reload
19
+ when :destroy_proxy_config
20
+ MKIt::HAProxy.delete_config_file(filename: job.data['filename'])
21
+ MKIt::HAProxy.reload
22
+ when :create_proxy_config
23
+ MKIt::HAProxy.create_config_file(filename: config[:filename], data: config[:data])
24
+ MKIt::HAProxy.reload
25
+ when :reload_proxy
26
+ MKIt::HAProxy.reload
27
+ when :restart_proxy
28
+ MKIt::HAProxy.restart
29
+ else
30
+ MKItLogger.warn("#{self.class} <<TODO>> job #{job.inspect}....")
31
+ end
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,39 @@
1
+ #
2
+ module MKIt
3
+ class PodWorker < AWorker
4
+
5
+ def topics
6
+ %w{pod_network_connected pod_network_disconnected
7
+ pod_unhealthy
8
+ start_pod stop_pod update_pod deploy_pod destroy_pod
9
+ pod_ip_updated pod_destroyed
10
+ }
11
+ end
12
+
13
+ def do_the(job)
14
+ MKItLogger.info("#{self.class} <#{job.topic}> job #{job.inspect}....")
15
+ pod = Pod.find(job.pod_id) unless job.pod_id.nil?
16
+ case job.topic.to_sym
17
+ when :deploy_pod
18
+ MKItLogger.warn("#{self.class} @deprecated job #{job.inspect}....")
19
+ when :start_pod
20
+ pod.start
21
+ when :stop_pod
22
+ pod.stop
23
+ when :destroy_pod
24
+ pod.stop
25
+ pod.destroy
26
+ when :pod_destroyed
27
+ if Service.exists?(job.service_id)
28
+ MkitJob.publish(topic: :update_proxy_config, service_id: job.service_id)
29
+ end
30
+ when :pod_ip_updated
31
+ MkitJob.publish(topic: :update_proxy_config, service_id: job.service_id)
32
+ else
33
+ MKItLogger.info("#{self.class} <<TODO>> job #{job.inspect}....")
34
+ end
35
+ end
36
+
37
+ end
38
+ end
39
+
@@ -0,0 +1,27 @@
1
+ module MKIt
2
+ class ServiceWorker < AWorker
3
+
4
+ def topics
5
+ %w{start_service stop_service update_service delete_service}
6
+ end
7
+
8
+ def do_the(job)
9
+ MKItLogger.info("#{self.class} <#{job.topic}> job #{job.inspect}....")
10
+ srv = Service.find(job.service_id)
11
+ case job.topic.to_sym
12
+ when :start_service
13
+ srv.start
14
+ when :stop_service
15
+ srv.stop
16
+ when :update_service
17
+ MKItLogger.info("#{self.class} <#{job.topic}> <<TODO>> job #{job.inspect}....")
18
+ when :delete_service
19
+ Service.destroy(job.service_id)
20
+ else
21
+ MKItLogger.info("#{self.class} <#{job.topic}> <<TODO>> job #{job.inspect}....")
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+
@@ -0,0 +1,14 @@
1
+ require 'mkit/workers/aworker'
2
+ require 'mkit/workers/service_worker'
3
+ require 'mkit/workers/pod_worker'
4
+ require 'mkit/workers/haproxy_worker'
5
+
6
+ module MKIt
7
+ class WorkerManager
8
+ def self.register_workers
9
+ ServiceWorker.new
10
+ PodWorker.new
11
+ HAProxyWorker.new
12
+ end
13
+ end
14
+ end