haconiwa 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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")