Skip to content

Commit 5efc92f

Browse files
committed
从旧仓库迁移代码
1 parent 10bb13a commit 5efc92f

File tree

422 files changed

+199787
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

422 files changed

+199787
-0
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,9 @@
1313

1414
# Dependency directories (remove the comment below to include it)
1515
# vendor/
16+
17+
# GoLand
18+
.idea/
19+
20+
# Binary
21+
/minit

Dockerfile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM golang:1.14 AS builder
2+
ENV CGO_ENABLED 0
3+
WORKDIR /go/src/app
4+
ADD . .
5+
RUN go test -mod vendor -v
6+
RUN go build -mod vendor -o /minit
7+
8+
FROM alpine:3.12
9+
COPY --from=builder /minit /minit
10+
CMD ["/minit"]

README.md

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,264 @@
11
# minit
2+
23
一个用 Go 编写的进程管理工具,用以在容器内启动多个进程
4+
5+
## 获取镜像
6+
7+
`acicn/minit`
8+
9+
## 使用方法
10+
11+
使用多阶段 Dockerfile 来从上述镜像地址导入 `minit` 可执行程序
12+
13+
```dockerfile
14+
FROM acicn/minit:1.5.0 AS minit
15+
16+
FROM xxxxxxx
17+
18+
# 添加一份服务配置到 /etc/minit.d/
19+
ADD my-service.yml /etc/minit.d/my-service.yml
20+
# 这将从 minit 镜像中,将可执行文件 /minit 拷贝到最终镜像的 /minit 位置
21+
COPY --from=minit /minit /minit
22+
# 这将指定 /minit 作为主启动程序
23+
CMD ["/minit"]
24+
```
25+
26+
## 配置文件
27+
28+
配置文件默认从 `/etc/minit.d/*.yml` 读取
29+
30+
每个配置单元必须具有唯一的 `name`,控制台输出默认会分别记录在 `/var/log/minit` 文件夹内
31+
32+
允许使用 `---` 分割在单个 `yaml` 文件中,写入多条配置单元
33+
34+
当前支持以下类型
35+
36+
* `render`
37+
38+
`render` 类型配置单元最先运行(优先级 L1),一般用于渲染配置文件
39+
40+
如下示例
41+
42+
`/etc/minit.d/render-test.yml`
43+
44+
```yaml
45+
kind: render
46+
name: render-test
47+
files:
48+
- /tmp/*.txt
49+
```
50+
51+
`/tmp/sample.txt`
52+
53+
```text
54+
Hello, {{stringsToUpper .Env.HOME}}
55+
```
56+
57+
`minit` 启动时,会按照配置规则,渲染 `/tmp/sample.txt` 文件
58+
59+
由于容器用户默认为 `root`,因此 `/tmp/sample.txt` 文件会被渲染为
60+
61+
```text
62+
Hello, /ROOT
63+
```
64+
65+
可用渲染函数,参见代码中的 `pkg/tmplfuncs/tmplfuncs.go`
66+
67+
* `once`
68+
69+
`once` 类型的配置单元随后运行(优先级 L2),用于执行一次性进程
70+
71+
`/etc/minit.d/sample.yml`
72+
73+
```yaml
74+
kind: once
75+
name: once-sample
76+
dir: /work # 指定工作目录
77+
command:
78+
- echo
79+
- once
80+
```
81+
82+
* `daemon`
83+
84+
`daemon` 类型的配置单元,最后启动(优先级 L3),用于执行常驻进程
85+
86+
```yaml
87+
kind: daemon
88+
name: daemon-sample
89+
dir: /work # 指定工作目录
90+
count: 3 # 如果指定了 count,会启动多个副本
91+
command:
92+
- sleep
93+
- 9999
94+
```
95+
96+
* `cron`
97+
98+
`cron` 类型的配置单元,最后启动(优先级 L3),用于按照 cron 表达式,执行命令
99+
100+
```yaml
101+
kind: cron
102+
name: cron-sample
103+
cron: "* * * * *"
104+
dir: /work # 指定工作目录
105+
command:
106+
- echo
107+
- cron
108+
```
109+
110+
* `logrotate`
111+
112+
**目前仍然不完备**
113+
114+
`logrotate` 类型的配置单元,最后启动(优先级 L3)
115+
116+
`logrotate` 会在每天凌晨执行以下动作
117+
118+
1. 寻找 `files` 字段指定的,不包含 `YYYY-MM-DD` 标记的文件,进行按日重命名
119+
2. 按照 `keep` 字段删除过期日
120+
3. 在 `dir` 目录执行 `command`
121+
122+
```yaml
123+
kind: logrotate
124+
name: logrotate-example
125+
files:
126+
- /app/logs/*.log
127+
- /app/logs/*/*.log
128+
- /app/logs/*/*/*.log
129+
- /app/logs/*/*/*/*.log
130+
mode: daily # 默认 daily, 可以设置为 filesize, 以 256 MB 为单元进行分割
131+
keep: 4 # 保留 4 天,或者 4 个分割文件
132+
# 完成 rotation 之后要执行的命令
133+
dir: /tmp
134+
command:
135+
- touch
136+
- xlog.reopen.txt
137+
```
138+
139+
## 使用 `Shell`
140+
141+
上述配置单元的 `command` 数组默认状态下等价于 `argv` 系统调用,如果想要使用基于 `Shell` 的多行命令,使用以下方式
142+
143+
```yaml
144+
name: demo-for-shell
145+
kind: once
146+
# 追加要使用的 shell
147+
shell: "/bin/bash -eu"
148+
command:
149+
- if [ -n "${HELLO}" ]; then
150+
- echo "world"
151+
- fi
152+
```
153+
154+
支持所有带 `command` 参数的工作单元类型,比如 `once`, `daemon`, `cron`
155+
156+
## 快速创建单元
157+
158+
如果懒得写 `YAML` 文件,可以直接用环境变量,或者 `CMD` 来创建 `daemon` 类型的配置单元
159+
160+
**使用环境变量创建单元**
161+
162+
```
163+
MINIT_MAIN=redis-server /etc/redis.conf
164+
MINIT_MAIN_DIR=/work
165+
MINIT_MAIN_NAME=main-program
166+
MINIT_MAIN_GROUP=super-main
167+
MINIT_MAIN_ONCE=false
168+
```
169+
170+
**使用命令行参数创建单元**
171+
172+
```
173+
CMD ["/minit", "--", "redis-server", "/etc/redis.conf"]
174+
```
175+
176+
## 打开/关闭单元
177+
178+
可以通过环境变量,打开/关闭特定的单元
179+
180+
* `MINIT_ENABLE`, 逗号分隔, 如果值存在,则为 `白名单模式`,只有指定名称的单元会执行
181+
* `MINIT_DISABLE`, 逗号分隔, 如果值存在,则为 `黑名单模式`,除了指定名称外的单元会执行
182+
183+
可以为配置单元设置字段 `group`,然后在上述环境变量使用 `@group` ,设置一组单元的开启和关闭。
184+
185+
没有设置 `group` 字段的单元,默认组名为 `default`
186+
187+
## 快速退出
188+
189+
默认情况下,即便是没有 L3 类型任务 (`daemon`, `cron`, `logrotate` 等),`minit` 也会持续运行,以支撑起容器主进程。
190+
191+
如果要在 `initContainers` 中,或者容器外使用 `minit`,可以将环境变量 `MINIT_QUICK_EXIT` 设置为 `true`
192+
193+
此时,如果没有 L3 类型任务,`minit` 会自动退出
194+
195+
## 资源限制 (ulimit)
196+
197+
**注意,使用此功能可能需要容器运行在高权限 (Privileged) 模式**
198+
199+
使用环境变量 `MINIT_RLIMIT_XXXX` 来设置容器的资源限制,`unlimited` 代表无限制, `-` 表示不修改
200+
201+
比如:
202+
203+
```
204+
MINIT_RLIMIT_NOFILE=unlimited # 同时设置软硬限制为 unlimited
205+
MINIT_RLIMIT_NOFILE=128:unlimited # 设置软限制为 128,设置硬限制为 unlimited
206+
MINIT_RLIMIT_NOFILE=128:- # 设置软限制为 128,硬限制不变
207+
MINIT_RLIMIT_NOFILE=-:unlimited # 软限制不变,硬限制修改为 unlimited
208+
```
209+
210+
可用的环境变量有:
211+
212+
```
213+
MINIT_RLIMIT_AS
214+
MINIT_RLIMIT_CORE
215+
MINIT_RLIMIT_CPU
216+
MINIT_RLIMIT_DATA
217+
MINIT_RLIMIT_FSIZE
218+
MINIT_RLIMIT_LOCKS
219+
MINIT_RLIMIT_MEMLOCK
220+
MINIT_RLIMIT_MSGQUEUE
221+
MINIT_RLIMIT_NICE
222+
MINIT_RLIMIT_NOFILE
223+
MINIT_RLIMIT_NPROC
224+
MINIT_RLIMIT_RTPRIO
225+
MINIT_RLIMIT_SIGPENDING
226+
MINIT_RLIMIT_STACK
227+
```
228+
229+
## 内核参数 (sysctl)
230+
231+
**注意,使用此功能可能需要容器运行在高权限 (Privileged) 模式**
232+
233+
使用环境变量 `MINIT_SYSCTL` 来写入 `sysctl` 配置项,`minit` 会自动写入 `/proc/sys` 目录下对应的参数
234+
235+
使用 `,` 分隔多个值
236+
237+
比如:
238+
239+
```
240+
MINIT_SYSCTL=vm.max_map_count=262144,vm.swappiness=60
241+
```
242+
243+
## 透明大页 (THP)
244+
245+
**注意,使用此功能可能需要容器运行在高权限 (Privileged) 模式,并且需要挂载 /sys 目录**
246+
247+
使用环境变量 `MINIT_THP` 修改 透明大页配置,可选值为 `never`, `madvise` 和 `always`
248+
249+
## WebDAV 服务
250+
251+
我懂你的痛,当你在容器里面生成了一份调试信息,比如 `Arthas` 或者 `Go pprof` 的火焰图,然后你开始绞尽脑汁想办法把这个文件传输出来
252+
253+
现在,不再需要这份痛苦了,`minit` 内置 `WebDAV` 服务,你可以像暴露一个标准服务一样暴露出来,省去了调度主机+映射主机目录等一堆烦心事
254+
255+
环境变量:
256+
257+
* `MINIT_WEBDAV_ROOT` 指定要暴露的路径并启动 WebDAV 服务,比如 `/srv`
258+
* `MINIT_WEBDAV_PORT` 指定 `WebDAV` 服务的端口,默认为 `7486`
259+
* `MINIT_WEBDAV_USERNAME` 和 `MINIT_WEBDAV_PASSWORD` 指定 `WebDAV` 服务的用户密码,默认不设置用户密码
260+
261+
## 许可证
262+
263+
Guo Y.K., MIT License
264+

execute.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"github.com/acicn/minit/pkg/mlog"
6+
"github.com/acicn/minit/pkg/shellquote"
7+
"io"
8+
"os"
9+
"os/exec"
10+
"strings"
11+
"sync"
12+
)
13+
14+
var (
15+
childPids = map[int]bool{}
16+
childPidsLock sync.Locker = &sync.Mutex{}
17+
)
18+
19+
type ExecuteOptions struct {
20+
Dir string `yaml:"dir"` // 所有涉及命令执行的单元,指定命令执行时的当前目录
21+
Shell string `yaml:"shell"` // 使用 shell 来执行命令,比如 'bash'
22+
Command []string `yaml:"command"` // 所有涉及命令执行的单元,指定命令执行的内容
23+
}
24+
25+
func addPid(pid int) {
26+
childPidsLock.Lock()
27+
defer childPidsLock.Unlock()
28+
childPids[pid] = true
29+
}
30+
31+
func removePid(pid int) {
32+
childPidsLock.Lock()
33+
defer childPidsLock.Unlock()
34+
delete(childPids, pid)
35+
}
36+
37+
func notifyPIDs(sig os.Signal) {
38+
childPidsLock.Lock()
39+
defer childPidsLock.Unlock()
40+
for pid, found := range childPids {
41+
if found {
42+
if process, _ := os.FindProcess(pid); process != nil {
43+
_ = process.Signal(sig)
44+
}
45+
}
46+
}
47+
}
48+
49+
func execute(opts ExecuteOptions, logger *mlog.Logger) (err error) {
50+
argv := make([]string, 0)
51+
52+
// 构建 argv
53+
if opts.Shell != "" {
54+
if argv, err = shellquote.Split(opts.Shell); err != nil {
55+
err = fmt.Errorf("无法处理 shell 参数,请检查: %s", err.Error())
56+
return
57+
}
58+
} else {
59+
for _, arg := range opts.Command {
60+
argv = append(argv, os.ExpandEnv(arg))
61+
}
62+
}
63+
64+
// 构建 cmd
65+
var outPipe, errPipe io.ReadCloser
66+
cmd := exec.Command(argv[0], argv[1:]...)
67+
if opts.Shell != "" {
68+
cmd.Stdin = strings.NewReader(strings.Join(opts.Command, "\n"))
69+
}
70+
cmd.Dir = opts.Dir
71+
// 阻止信号传递
72+
setupCmdSysProcAttr(cmd)
73+
74+
if outPipe, err = cmd.StdoutPipe(); err != nil {
75+
return
76+
}
77+
if errPipe, err = cmd.StderrPipe(); err != nil {
78+
return
79+
}
80+
81+
// 执行
82+
if err = cmd.Start(); err != nil {
83+
return
84+
}
85+
86+
// 记录 Pid
87+
addPid(cmd.Process.Pid)
88+
89+
// 串流
90+
go logger.StreamOut(outPipe)
91+
go logger.StreamErr(errPipe)
92+
93+
// 等待退出
94+
if err = cmd.Wait(); err != nil {
95+
logger.Errorf("进程退出: %s", err.Error())
96+
err = nil
97+
} else {
98+
logger.Printf("进程退出")
99+
}
100+
101+
// 移除 Pid
102+
removePid(cmd.Process.Pid)
103+
104+
return
105+
}

0 commit comments

Comments
 (0)