├── .dockerignore ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── README.markdown ├── build-release.sh ├── src └── main.rs └── test /.dockerignore: -------------------------------------------------------------------------------- 1 | target 2 | lovely_touching 3 | my_init 4 | Dockerfile 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /my_init 3 | /lovely_touching 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "lovely_touching" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "bitflags 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "getopts 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 7 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 8 | ] 9 | 10 | [[package]] 11 | name = "bitflags" 12 | version = "0.3.0" 13 | source = "registry+https://github.com/rust-lang/crates.io-index" 14 | 15 | [[package]] 16 | name = "getopts" 17 | version = "0.2.11" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | dependencies = [ 20 | "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 21 | ] 22 | 23 | [[package]] 24 | name = "libc" 25 | version = "0.1.8" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | 28 | [[package]] 29 | name = "log" 30 | version = "0.3.1" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | dependencies = [ 33 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 34 | ] 35 | 36 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lovely_touching" 3 | version = "0.1.0" 4 | authors = ["James Pike "] 5 | 6 | [dependencies] 7 | getopts = "^0.2" 8 | libc = "^0.1" 9 | bitflags = "^0.3" 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:5 2 | MAINTAINER James Pike version: 0.1 3 | 4 | RUN yum install -y curl file gcc && curl -sSf http://static.rust-lang.org/rustup.sh | sed "s/https:/http:/" | sh -s - --disable-sudo -y --prefix=/usr 5 | ADD . /lovely_touching 6 | RUN cd lovely_touching && cargo build --release 7 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # A tiny init for docker containers 2 | 3 | Hey maybe your Docker file contains something like this: 4 | 5 | ``` 6 | CMD ["/bin/node", "app.js"] 7 | ``` 8 | 9 | Oh no [this is no good!](https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/) - TLDR: Your process can get shutdown in a bad way and *zombie processes* will eat your container! 10 | 11 | Instead just use `lovely_touching`: 12 | 13 | 1. Download it (this binary will work on any Linux version from Centos/Redhat 5 and up): 14 | 15 | ``` 16 | wget https://github.com/ohjames/lovely_touching/releases/download/v0.1.0/lovely_touching 17 | chmod a+x lovely_touching 18 | ``` 19 | 20 | 2. Change your `Dockerfile` to this: 21 | 22 | ``` 23 | ADD lovely_touching /bin/lovely_touching 24 | CMD ["/bin/lovely_touching", "--", "/bin/node", "app.js" ] 25 | ``` 26 | 27 | Now you don't have to worry anymore! 28 | 29 | `lovely_touching` is written in `rust` so you don't need anything installed in the host machine to use the binary. 30 | 31 | ## Building 32 | 33 | ``` 34 | cargo build --release 35 | ``` 36 | 37 | Or to build a copy against Centos5's glibc (so it can run on more machines) first install docker, then run this as a user with access to docker: 38 | 39 | ``` 40 | ./build-release.sh 41 | ``` 42 | 43 | ## More stuff 44 | 45 | If you want to run multiple processes you can separate them with the argument `---`: 46 | ``` 47 | ADD lovely_touching /bin/lovely_touching 48 | CMD ["/bin/lovely_touching", "--", "/bin/runit", "---", "/bin/node", "app.js" ] 49 | ``` 50 | 51 | If you want an init system with even less overhead then try [smell-baron](https://github.com/ohjames/smell-baron) which implements the same functionality in C. The binary ends up being around 8k instead of the 800k needed by the rust runtime. 52 | -------------------------------------------------------------------------------- /build-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | docker build -t rustc5 . || exit 1 4 | docker run rustc5 true || exit 1 5 | docker cp $(docker ps -aq | head -n1):/lovely_touching/target/release/lovely_touching . 6 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | extern crate getopts; 3 | 4 | use std::ffi::CString; 5 | use std::env; 6 | use std::ptr; 7 | use std::mem; 8 | use std::thread; 9 | 10 | pub use libc::consts::os::posix88::{SIGTERM,SIGINT}; 11 | use libc::funcs::posix88::unistd::waitpid; 12 | use libc::funcs::posix88::unistd::fork; 13 | use libc::funcs::posix88::unistd::execvp; 14 | use libc::types::os::arch::posix88::pid_t; 15 | use getopts::Options; 16 | 17 | // signal handling { 18 | #[macro_use] 19 | extern crate bitflags; 20 | 21 | bitflags!( 22 | #[repr(C)] 23 | flags SockFlag: libc::c_ulong { 24 | const SA_NOCLDSTOP = 0x00000001, 25 | const SA_NOCLDWAIT = 0x00000002, 26 | const SA_NODEFER = 0x40000000, 27 | const SA_ONSTACK = 0x08000000, 28 | const SA_RESETHAND = 0x80000000, 29 | const SA_RESTART = 0x10000000, 30 | const SA_SIGINFO = 0x00000004, 31 | } 32 | ); 33 | 34 | 35 | #[repr(C)] 36 | #[cfg(target_pointer_width = "32")] 37 | #[derive(Clone, Copy)] 38 | pub struct sigset_t { 39 | __val: [libc::c_ulong; 32], 40 | } 41 | 42 | #[repr(C)] 43 | #[cfg(target_pointer_width = "64")] 44 | #[derive(Clone, Copy)] 45 | pub struct sigset_t { 46 | __val: [libc::c_ulong; 16], 47 | } 48 | 49 | #[repr(C)] 50 | #[allow(missing_copy_implementations)] 51 | pub struct sigaction { 52 | pub sa_handler: extern fn(libc::c_int), 53 | pub sa_mask: sigset_t, 54 | pub sa_flags: SockFlag, 55 | sa_restorer: *mut libc::c_void, 56 | } 57 | 58 | extern { 59 | pub fn sigaction( 60 | signum: libc::c_int, 61 | act: *const sigaction, 62 | oldact: *mut sigaction 63 | ) -> libc::c_int; 64 | } 65 | // } 66 | 67 | #[test] 68 | fn it_works() { 69 | } 70 | 71 | static mut RUNNING: bool = true; 72 | const WNOHANG: libc::c_int = 1; 73 | // how often to check if the process should halt in milliseconds 74 | const HALT_RESOLUTION: u32 = 100; 75 | 76 | fn wait_for_commands_to_exit(pids: &mut Vec) { 77 | loop { 78 | let status: i32 = 0; 79 | let waited_pid: pid_t; 80 | unsafe { 81 | waited_pid = waitpid(-1, &status, WNOHANG); 82 | } 83 | if waited_pid == 0 { 84 | thread::sleep_ms(HALT_RESOLUTION); 85 | } 86 | else { 87 | match pids.iter().position(|p| *p == waited_pid) { 88 | Some(idx) => { 89 | pids.remove(idx); 90 | println!("exited {}", waited_pid); 91 | if pids.is_empty() { 92 | return; 93 | } 94 | } 95 | None => {} 96 | } 97 | } 98 | 99 | unsafe { 100 | if ! RUNNING { 101 | println!("terminated - TODO: send SIGTERM to children"); 102 | return; 103 | } 104 | } 105 | } 106 | } 107 | 108 | fn print_usage(program: &str, opts: Options) { 109 | let brief = format!("usage: {} [options] program [arguments]", program); 110 | print!("{}", opts.usage(&brief)); 111 | } 112 | 113 | fn run_commands(cmd_and_args: &Vec) -> Vec { 114 | println!("running `{}'", cmd_and_args.connect(" ")); 115 | 116 | let mut ret = Vec::::new(); 117 | 118 | let cmds = cmd_and_args.split(|s| s == "---"); 119 | for cmd in cmds { 120 | let pid; 121 | unsafe { 122 | pid = fork(); 123 | } 124 | 125 | if pid == 0 { 126 | let mut cstrings = Vec::::new(); 127 | let mut arg_ptrs = Vec::<*const i8>::new(); 128 | 129 | cstrings.reserve(cmd.len()); 130 | arg_ptrs.reserve(cmd.len() + 1); 131 | 132 | for arg in cmd.iter() { 133 | cstrings.push(CString::new(arg.clone()).unwrap()); 134 | arg_ptrs.push(cstrings.last().unwrap().as_ptr()); 135 | } 136 | arg_ptrs.push(ptr::null()); 137 | 138 | unsafe { 139 | execvp( 140 | CString::new(cmd[0].clone()).unwrap().as_ptr(), 141 | arg_ptrs.as_mut_ptr() 142 | ); 143 | } 144 | panic!("execvp failed"); 145 | } 146 | else { 147 | ret.push(pid); 148 | } 149 | 150 | } 151 | 152 | return ret; 153 | } 154 | 155 | extern fn accept_term(_signal: libc::c_int) { 156 | unsafe { 157 | RUNNING = false; 158 | } 159 | } 160 | 161 | fn main() { 162 | let args: Vec = env::args().collect(); 163 | let program = args[0].clone(); 164 | let mut opts = Options::new(); 165 | opts.optflag("h", "help", "show help"); 166 | let matches = match opts.parse(&args[1..]) { 167 | Ok(m) => { m } 168 | Err(f) => { panic!(f.to_string()) } 169 | }; 170 | 171 | if matches.opt_present("h") { 172 | print_usage(&program, opts); 173 | return; 174 | } 175 | 176 | if matches.free.len() == 0 { 177 | println!("must specify a command to run"); 178 | print_usage(&program, opts); 179 | return; 180 | } 181 | 182 | let mut pids: Vec; 183 | let mut sa = unsafe { mem::zeroed::() }; 184 | sa.sa_handler = accept_term; 185 | 186 | unsafe { 187 | sigaction(SIGTERM, &sa, ptr::null_mut()); 188 | sigaction(SIGINT, &sa, ptr::null_mut()); 189 | } 190 | 191 | pids = run_commands(&matches.free); 192 | println!("pids are {:?}", pids); 193 | wait_for_commands_to_exit(&mut pids); 194 | } 195 | -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- 1 | target/debug/lovely_touching --------------------------------------------------------------------------------