haconiwa 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9ddb95a0442d9007bda5a95609229cf0cef771a6
4
- data.tar.gz: e56fd0275206c68766efc6e7c27a406aaf5db059
3
+ metadata.gz: fa21fa8f08a50b66d878955312eb2537b1a8c44d
4
+ data.tar.gz: 378fe5fe360b75569622b78a9cf0c1c0e5300659
5
5
  SHA512:
6
- metadata.gz: 96efacf5d423f067a4fcf33bc624d32db67bb803c9b3123f93c546e4acf435bef82fc48fe58395a84a8eaec3556ac19651b09385102f8631b89e1bb71bb4b541
7
- data.tar.gz: aeaadfc43ff779eb60d295e05a7f10589795f1e520ac453a426d31b4cc181ba00bc5d9d439fc110e29dafcfeb1995aa4eef4c5d1459768c0a026ee80923b4602
6
+ metadata.gz: 575dcfbc3d35f4c8800e3b865c82fa96f785dc422f8481c6d7b1c783579a846c391514a9f0aefa9bde71c85d00fde2e36ec325899b2428b229899306e8dba708
7
+ data.tar.gz: df4a100b6a234b8e6fd42550f2078cbb7ca916619a5de9e9b1c74d1ce306f78b9c528f3c30ad92fc2c5941e5585e4ff306354da81e2c3231428d1688817e9826
data/README.md CHANGED
@@ -6,51 +6,99 @@ Ruby on Container / helper tools with DSL for your handmade linux containers
6
6
 
7
7
  ## Installation
8
8
 
9
- Add this line to your application's Gemfile:
9
+ This gem only works on Linux systems, as using cgroup, Linux namespace and Linux capabilities.
10
10
 
11
- ```ruby
12
- gem 'haconiwa'
13
- ```
11
+ Some packages/dylibs/tools are used internally, so please install below before gem setup:
14
12
 
15
- And then execute:
13
+ * `libcap` (`libcap.so.2` is used via FFI)
14
+ * `nsenter` (e.g. `yum install util-linux` or such one)
16
15
 
17
- $ bundle
16
+ Then, install it yourself globally(recommended using rbenv) as:
18
17
 
19
- Or install it yourself as:
18
+ ```console
19
+ $ gem install haconiwa
20
+ $ rbenv rehash # if needed
21
+ ```
22
+
23
+ Or add the line to your application's Gemfile:
24
+
25
+ ```ruby
26
+ gem 'haconiwa'
27
+ ```
20
28
 
21
- $ gem install haconiwa
29
+ And then execute `bundle`.
22
30
 
23
31
  ## Usage
24
32
 
25
- ```ruby
26
- require "haconiwa"
33
+ Create the file `example001.haco`:
27
34
 
28
- haconiwa = Haconiwa::Base.define do |config|
35
+ ```ruby
36
+ Haconiwa::Base.define do |config|
29
37
  config.name = "new-haconiwa001" # to be hostname
30
38
 
31
39
  config.cgroup["cpu.shares"] = 2048
32
40
  config.cgroup["memory.limit_in_bytes"] = "256M"
33
41
  config.cgroup["pid.max"] = 1024
34
42
 
43
+ config.add_mount_point "/var/another/root/etc", to: "/var/your_rootfs/etc", readonly: true
44
+ config.add_mount_point "/var/another/root/home", to: "/var/your_rootfs/home"
45
+ config.mount_independent_procfs
35
46
  config.chroot_to "/var/your_rootfs"
36
- config.add_mount_point "/var/another/root/etc", to: "/etc", readonly: true
37
- config.add_mount_point "/var/another/root/home", to: "/home"
38
- config.add_mount_point "proc", to: "/proc", fs: "proc"
39
47
 
40
48
  config.namespace.unshare "ipc"
49
+ config.namespace.unshare "uts"
41
50
  config.namespace.unshare "mount"
42
51
  config.namespace.unshare "pid"
43
- config.namespace.use_netns "foobar"
44
52
 
45
53
  config.capabilities.allow :all
46
54
  config.capabilities.drop "CAP_SYS_TIME"
47
55
  end
56
+ ```
57
+
58
+ Then use `haconiwa` binary installed with thie gem.
59
+
60
+ ```console
61
+ $ haconiwa run example001.haco
62
+ ```
63
+
64
+ When you want to attach existing container:
65
+
66
+ ```console
67
+ $ haconiwa attach example001.haco
68
+ ```
69
+
70
+ Note: `attach` subcommand allows to set PID(`--target`) or container name(`--name`) for dynamic configuration.
71
+ And `attach` is not concerned with capabilities which is granted to container. So you can drop or allow specific caps with `--drop/--allow`.
72
+
73
+ ### DSL spec
74
+
75
+ * `config.cgroup` - Assign cgroup parameters via `[]=`
76
+ * `config.namespace.unshare` - Unshare the namespaces like `"mount"`, `"ipc"` or `"pid"`
77
+ * `config.capabilities.allow` - Allow capabilities on container root. Setting parameters other than `:all` should make this acts as whitelist
78
+ * `config.capabilities.drop` - Drop capabilities of container root. Default to act as blacklist
79
+ * `config.add_mount_point` - Add the mount point odf container
80
+ * `config.mount_independent_procfs` - Mount the independent /proc directory in the container. Useful if `"pid"` is unshared
81
+ * `config.chroot_to` - The new chroot root
48
82
 
49
- haconiwa.start
83
+ You can pick your own parameters for your use case of container.
84
+ e.g. just using `mount` namespace unshared, container with common filesystem, limit the cgroups for big resource job and so on.
50
85
 
51
- ## or to attach running container
86
+ Please look into `example` directory.
52
87
 
53
- Haconiwa.attach haconiwa.name
88
+ ### Library use case
89
+
90
+ ```ruby
91
+ require 'haconiwa'
92
+ base = Haconiwa::Base.define do |config|
93
+ config.name = "new-haconiwa001" # to be hostname
94
+ ...
95
+ end
96
+
97
+ # Run the container
98
+ base.run("/bin/bash")
99
+
100
+ # Or attach existing one
101
+ base.attach("/bin/bash")
54
102
  ```
55
103
 
56
104
  ## Development
@@ -63,8 +111,13 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
63
111
 
64
112
  Bug reports and pull requests are welcome on GitHub at https://github.com/udzura/haconiwa. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
65
113
 
114
+ ## TODOs
115
+
116
+ * [ ] netns attachment
117
+ * [ ] more utilities such as `ps`
118
+ * [ ] better daemon handling
66
119
 
67
120
  ## License
68
121
 
69
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
122
+ This gem is created under [GNU General Public License Version 3](./LICENSE).
70
123
 
@@ -1,5 +1,4 @@
1
- require 'haconiwa'
2
- require 'pathname'
1
+ # -*- mode: ruby -*-
3
2
  haconiwa = Haconiwa::Base.define do |config|
4
3
  config.name = "cpu-quota001" # to be hostname
5
4
 
@@ -20,5 +19,3 @@ haconiwa = Haconiwa::Base.define do |config|
20
19
  config.cgroup["cpu.cfs_period_us"] = 100000
21
20
  config.cgroup["cpu.cfs_quota_us"] = 30000
22
21
  end
23
-
24
- haconiwa.start("/bin/bash")
@@ -1,5 +1,4 @@
1
- require 'haconiwa'
2
- require 'pathname'
1
+ # -*- mode: ruby -*-
3
2
  haconiwa = Haconiwa::Base.define do |config|
4
3
  config.name = "drop-time001" # to be hostname
5
4
 
@@ -20,6 +19,7 @@ haconiwa = Haconiwa::Base.define do |config|
20
19
 
21
20
  config.capabilities.allow :all
22
21
  config.capabilities.drop "cap_sys_time"
23
- end
24
22
 
25
- haconiwa.start("/bin/bash")
23
+ # or use white list:
24
+ # config.capabilities.allow "cap_sys_time"
25
+ end
data/exe/haconiwa CHANGED
@@ -8,4 +8,17 @@ when "version"
8
8
  puts Haconiwa::VERSION
9
9
  when "run"
10
10
  Haconiwa::Cli.run(ARGV[1..-1])
11
+ when "attach"
12
+ Haconiwa::Cli.attach(ARGV[1..-1])
13
+ else
14
+ STDERR.puts <<-EOH
15
+ Usage of #{$0}:
16
+ run - Run the container with init command
17
+ Example: haconiwa run path/to/definition.haco -- /bin/your command
18
+ attach - Attach to the existing container with command
19
+ Example: haconiwa attach path/to/definition.haco -- /bin/your command
20
+
21
+ Version:
22
+ #{Haconiwa::VERSION}
23
+ EOH
11
24
  end
data/lib/haconiwa/base.rb CHANGED
@@ -10,10 +10,12 @@ module Haconiwa
10
10
  attr_accessor :name,
11
11
  :init_command,
12
12
  :container_pid_file,
13
+ :pid,
13
14
  :filesystem,
14
15
  :cgroup,
15
16
  :namespace,
16
- :capabilities
17
+ :capabilities,
18
+ :attached_capabilities
17
19
 
18
20
  def self.define(&b)
19
21
  new.tap(&b)
@@ -24,8 +26,10 @@ module Haconiwa
24
26
  @cgroup = CGroup.new
25
27
  @namespace = Namespace.new
26
28
  @capabilities = Capabilities.new
29
+ @attached_capabilities = nil
27
30
  @name = "haconiwa-#{Time.now.to_i}"
28
- @container_pid_file = "/var/run/haconiwa-#{@name}.pid"
31
+ @container_pid_file = nil
32
+ @pid = nil
29
33
  end
30
34
 
31
35
  # aliases
@@ -44,9 +48,19 @@ module Haconiwa
44
48
  end
45
49
 
46
50
  def start(*init_command)
51
+ self.container_pid_file ||= default_container_pid_file
47
52
  Runners::Linux.run(self, init_command)
48
53
  end
49
54
  alias run start
55
+
56
+ def attach(*run_command)
57
+ self.container_pid_file ||= default_container_pid_file
58
+ Runners::Linux.attach(self, run_command)
59
+ end
60
+
61
+ def default_container_pid_file
62
+ "/var/run/haconiwa-#{@name}.pid"
63
+ end
50
64
  end
51
65
 
52
66
  def self.define(&b)
@@ -18,6 +18,7 @@ module Haconiwa
18
18
  def to_dirs
19
19
  groups.keys.map{|k| k.split('.').first }.uniq
20
20
  end
21
+ alias dirs to_dirs
21
22
 
22
23
  def register_all!(to: nil)
23
24
  cg = SmallCgroup.new(name: to)
@@ -25,5 +26,12 @@ module Haconiwa
25
26
  cg.register k, v
26
27
  end
27
28
  end
29
+
30
+ def attach(to: nil)
31
+ cg = SmallCgroup.new(name: to)
32
+ dirs.each do |dir|
33
+ cg.attach(dir)
34
+ end
35
+ end
28
36
  end
29
37
  end
data/lib/haconiwa/cli.rb CHANGED
@@ -1,15 +1,48 @@
1
1
  module Haconiwa
2
2
  module Cli
3
3
  def self.run(args)
4
+ base, init = get_script_and_eval(args)
5
+ base.run(*init)
6
+ end
7
+
8
+ def self.attach(args)
9
+ require 'optparse'
10
+ opt = OptionParser.new
11
+ pid = nil
12
+ name = nil
13
+ allow = nil
14
+ drop = nil
15
+
16
+ opt.program_name = "haconiwa attach"
17
+ opt.on('-t', '--target PID', "Container's PID to attatch. If not set, use pid file of definition") {|v| pid = v }
18
+ opt.on('-n', '--name CONTAINER_NAME', "Container's name. Set if the name is dynamically defined") {|v| name = v }
19
+ opt.on('--allow CAPS[,CAPS...]', "Capabilities to allow attached process. Independent container's own caps") {|v| allow = v.split(',') }
20
+ opt.on('--drop CAPS[,CAPS...]', "Capabilities to drop from attached process. Independent container's own caps") {|v| drop = v.split(',') }
21
+ args = opt.parse(args)
22
+
23
+ base, exe = get_script_and_eval(args)
24
+ base.pid = pid if pid
25
+ base.name = name if name
26
+ if allow || drop
27
+ base.attached_capabilities = Capabilities.new
28
+ base.attached_capabilities.allow(*allow) if allow
29
+ base.attached_capabilities.drop(*drop) if drop
30
+ end
31
+
32
+ base.attach(*exe)
33
+ end
34
+
35
+ private
36
+
37
+ def self.get_script_and_eval(args)
4
38
  require 'pathname'
5
39
  script = File.read(args[0])
6
- init = args[1..-1]
7
- if init.first == "--"
8
- init.shift
40
+ exe = args[1..-1]
41
+ if exe.first == "--"
42
+ exe.shift
9
43
  end
10
44
 
11
- container = eval(script)
12
- container.run(*init)
45
+ return [eval(script), exe]
13
46
  end
14
47
  end
15
48
  end
@@ -1,6 +1,7 @@
1
1
  module Haconiwa
2
2
  class Namespace
3
3
  UNSHARE = 272
4
+ SETNS = 308
4
5
 
5
6
  # from linux/sched.h
6
7
 
@@ -25,6 +26,16 @@ module Haconiwa
25
26
  "uts" => CLONE_NEWUTS,
26
27
  }
27
28
 
29
+ FLAG_TO_PARAM = {
30
+ CLONE_NEWCGROUP => "cgroup",
31
+ CLONE_NEWIPC => "ipc",
32
+ CLONE_NEWNET => "net",
33
+ CLONE_NEWNS => "mount",
34
+ CLONE_NEWPID => "pid",
35
+ CLONE_NEWUSER => "user",
36
+ CLONE_NEWUTS => "uts",
37
+ }
38
+
28
39
  def initialize
29
40
  @use_ns = []
30
41
  @netns_name = nil
@@ -55,8 +66,20 @@ module Haconiwa
55
66
  Kernel.syscall(UNSHARE, flag)
56
67
  end
57
68
 
69
+ def enter(pid: nil, wrapper_path: nil)
70
+ ns_params = use_ns_all.map{|f| "--#{FLAG_TO_PARAM[f]}" }
71
+ exec "nsenter",
72
+ "--target", "#{pid}",
73
+ *ns_params,
74
+ "--", wrapper_path.to_s
75
+ end
76
+
58
77
  private
59
78
 
79
+ def use_ns_all
80
+ @use_ns.uniq + (@use_pid_ns ? [CLONE_NEWPID] : [])
81
+ end
82
+
60
83
  def to_ns_flag
61
84
  @use_ns.inject(0x00000000) { |dst, flag|
62
85
  dst |= flag
@@ -1,5 +1,6 @@
1
1
  require 'tempfile'
2
2
  require 'fileutils'
3
+ require 'shellwords'
3
4
  require 'bundler'
4
5
 
5
6
  module Haconiwa::Runners
@@ -53,7 +54,8 @@ module Haconiwa::Runners
53
54
  container
54
55
  end
55
56
  File.open(base.container_pid_file, "w") {|pid| pid.write real_container_pid }
56
- puts "New container: PID = #{real_container_pid}"
57
+ Signal.trap(:INT) { Process.kill :TERM, container }
58
+ puts "New container: Name = #{base.name}, PID = #{real_container_pid}"
57
59
 
58
60
  res = Process.waitpid2 container
59
61
  if res[1].success?
@@ -65,6 +67,42 @@ module Haconiwa::Runners
65
67
  at_exit { FileUtils.rm_f base.container_pid_file }
66
68
  end
67
69
 
70
+ def self.attach(base, run_command=[])
71
+ if run_command.empty?
72
+ run_command << "/bin/bash"
73
+ end
74
+
75
+ wrapper = Tempfile.open("haconiwa-attacher-#{$$}-#{Time.now.to_i}.sh")
76
+
77
+ wrapper.puts "#!/bin/bash"
78
+ wrapper.puts "chroot #{base.filesystem.chroot} bash -c '"
79
+ wrapper.puts "cd / ;"
80
+ wrapper.puts Shellwords.shelljoin(["exec", *run_command]).gsub(/'/, %<'"'"'>)
81
+ wrapper.puts "'"
82
+ wrapper.close
83
+ FileUtils.chmod 0700, wrapper.path
84
+
85
+ runner = fork {
86
+ base.pid ||= File.read(base.container_pid_file).to_i
87
+
88
+ if base.attached_capabilities
89
+ base.attached_capabilities.apply!
90
+ end
91
+
92
+ base.cgroup.attach(to: base.name)
93
+ Bundler.with_clean_env { base.namespace.enter(pid: base.pid, wrapper_path: wrapper.path) }
94
+ }
95
+
96
+ puts "Attached to contanier: Runner PID = #{runner}"
97
+
98
+ res = Process.waitpid2 runner
99
+ if res[1].success?
100
+ puts "Successfully exit."
101
+ else
102
+ puts "Attached process exited with status code <#{res[1].to_i}>."
103
+ end
104
+ end
105
+
68
106
  private
69
107
  def self.find_by_ppid(ppid)
70
108
  s = Dir.glob('/proc/*/stat')
@@ -31,10 +31,15 @@ module Haconiwa
31
31
  def activate(dir)
32
32
  dirroot = root_of(dir)
33
33
  FileUtils.mkdir_p dirroot
34
- append_write dirroot.join("tasks"), self.pid
34
+ attach(dir)
35
35
  @active_dirs << dir
36
36
  end
37
37
 
38
+ def attach(dir)
39
+ dirroot = root_of(dir)
40
+ append_write dirroot.join("tasks"), self.pid
41
+ end
42
+
38
43
  def activated?(dir)
39
44
  @active_dirs.include? dir
40
45
  end
@@ -1,3 +1,3 @@
1
1
  module Haconiwa
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: haconiwa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Uchio KONDO
@@ -121,16 +121,14 @@ files:
121
121
  - CODE_OF_CONDUCT.md
122
122
  - Gemfile
123
123
  - LICENSE
124
- - LICENSE.txt
125
124
  - README.md
126
125
  - Rakefile
127
126
  - Vagrantfile
128
127
  - bin/console
129
128
  - bin/setup
130
129
  - examples/chroot.haco
131
- - examples/chroot.rb
132
- - examples/cpu.rb
133
- - examples/drop_cap_sys_time.rb
130
+ - examples/cpu.haco
131
+ - examples/drop_cap_sys_time.haco
134
132
  - exe/haconiwa
135
133
  - haconiwa.gemspec
136
134
  - lib/haconiwa.rb
data/LICENSE.txt DELETED
@@ -1,21 +0,0 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2016 Uchio KONDO
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
data/examples/chroot.rb DELETED
@@ -1,21 +0,0 @@
1
- require 'haconiwa'
2
- require 'pathname'
3
- haconiwa = Haconiwa::Base.define do |config|
4
- config.name = "chroot001" # to be hostname
5
-
6
- root = Pathname.new("/var/haconiwa/root")
7
- config.add_mount_point "/var/haconiwa/rootfs", to: root, readonly: true
8
- config.add_mount_point "/lib64", to: root.join("lib64"), readonly: true
9
- config.add_mount_point "/usr/bin", to: root.join("usr/bin"), readonly: true
10
- config.add_mount_point "tmpfs", to: root.join("tmp"), fs: "tmpfs"
11
- config.add_mount_point "/var/haconiwa/user_homes/hakoniwa-test001/home/hakoniwa", to: root.join("home/hakoniwa")
12
- config.mount_independent_procfs
13
- config.chroot_to root
14
-
15
- config.namespace.unshare "mount"
16
- config.namespace.unshare "ipc"
17
- config.namespace.unshare "uts"
18
- config.namespace.unshare "pid"
19
- end
20
-
21
- haconiwa.start("/bin/bash")