Skip to content

Commit

Permalink
从旧仓库迁移代码
Browse files Browse the repository at this point in the history
  • Loading branch information
guoyk93 committed Nov 9, 2020
1 parent 10bb13a commit 5efc92f
Show file tree
Hide file tree
Showing 422 changed files with 199,787 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@

# Dependency directories (remove the comment below to include it)
# vendor/

# GoLand
.idea/

# Binary
/minit
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM golang:1.14 AS builder
ENV CGO_ENABLED 0
WORKDIR /go/src/app
ADD . .
RUN go test -mod vendor -v
RUN go build -mod vendor -o /minit

FROM alpine:3.12
COPY --from=builder /minit /minit
CMD ["/minit"]
262 changes: 262 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,264 @@
# minit

一个用 Go 编写的进程管理工具,用以在容器内启动多个进程

## 获取镜像

`acicn/minit`

## 使用方法

使用多阶段 Dockerfile 来从上述镜像地址导入 `minit` 可执行程序

```dockerfile
FROM acicn/minit:1.5.0 AS minit

FROM xxxxxxx

# 添加一份服务配置到 /etc/minit.d/
ADD my-service.yml /etc/minit.d/my-service.yml
# 这将从 minit 镜像中,将可执行文件 /minit 拷贝到最终镜像的 /minit 位置
COPY --from=minit /minit /minit
# 这将指定 /minit 作为主启动程序
CMD ["/minit"]
```

## 配置文件

配置文件默认从 `/etc/minit.d/*.yml` 读取

每个配置单元必须具有唯一的 `name`,控制台输出默认会分别记录在 `/var/log/minit` 文件夹内

允许使用 `---` 分割在单个 `yaml` 文件中,写入多条配置单元

当前支持以下类型

* `render`

`render` 类型配置单元最先运行(优先级 L1),一般用于渲染配置文件

如下示例

`/etc/minit.d/render-test.yml`

```yaml
kind: render
name: render-test
files:
- /tmp/*.txt
```
`/tmp/sample.txt`

```text
Hello, {{stringsToUpper .Env.HOME}}
```

`minit` 启动时,会按照配置规则,渲染 `/tmp/sample.txt` 文件

由于容器用户默认为 `root`,因此 `/tmp/sample.txt` 文件会被渲染为

```text
Hello, /ROOT
```

可用渲染函数,参见代码中的 `pkg/tmplfuncs/tmplfuncs.go`

* `once`

`once` 类型的配置单元随后运行(优先级 L2),用于执行一次性进程

`/etc/minit.d/sample.yml`

```yaml
kind: once
name: once-sample
dir: /work # 指定工作目录
command:
- echo
- once
```

* `daemon`

`daemon` 类型的配置单元,最后启动(优先级 L3),用于执行常驻进程

```yaml
kind: daemon
name: daemon-sample
dir: /work # 指定工作目录
count: 3 # 如果指定了 count,会启动多个副本
command:
- sleep
- 9999
```

* `cron`

`cron` 类型的配置单元,最后启动(优先级 L3),用于按照 cron 表达式,执行命令

```yaml
kind: cron
name: cron-sample
cron: "* * * * *"
dir: /work # 指定工作目录
command:
- echo
- cron
```

* `logrotate`

**目前仍然不完备**

`logrotate` 类型的配置单元,最后启动(优先级 L3)

`logrotate` 会在每天凌晨执行以下动作

1. 寻找 `files` 字段指定的,不包含 `YYYY-MM-DD` 标记的文件,进行按日重命名
2. 按照 `keep` 字段删除过期日
3. 在 `dir` 目录执行 `command`

```yaml
kind: logrotate
name: logrotate-example
files:
- /app/logs/*.log
- /app/logs/*/*.log
- /app/logs/*/*/*.log
- /app/logs/*/*/*/*.log
mode: daily # 默认 daily, 可以设置为 filesize, 以 256 MB 为单元进行分割
keep: 4 # 保留 4 天,或者 4 个分割文件
# 完成 rotation 之后要执行的命令
dir: /tmp
command:
- touch
- xlog.reopen.txt
```

## 使用 `Shell`

上述配置单元的 `command` 数组默认状态下等价于 `argv` 系统调用,如果想要使用基于 `Shell` 的多行命令,使用以下方式

```yaml
name: demo-for-shell
kind: once
# 追加要使用的 shell
shell: "/bin/bash -eu"
command:
- if [ -n "${HELLO}" ]; then
- echo "world"
- fi
```

支持所有带 `command` 参数的工作单元类型,比如 `once`, `daemon`, `cron`

## 快速创建单元

如果懒得写 `YAML` 文件,可以直接用环境变量,或者 `CMD` 来创建 `daemon` 类型的配置单元

**使用环境变量创建单元**

```
MINIT_MAIN=redis-server /etc/redis.conf
MINIT_MAIN_DIR=/work
MINIT_MAIN_NAME=main-program
MINIT_MAIN_GROUP=super-main
MINIT_MAIN_ONCE=false
```
**使用命令行参数创建单元**
```
CMD ["/minit", "--", "redis-server", "/etc/redis.conf"]
```
## 打开/关闭单元
可以通过环境变量,打开/关闭特定的单元
* `MINIT_ENABLE`, 逗号分隔, 如果值存在,则为 `白名单模式`,只有指定名称的单元会执行
* `MINIT_DISABLE`, 逗号分隔, 如果值存在,则为 `黑名单模式`,除了指定名称外的单元会执行
可以为配置单元设置字段 `group`,然后在上述环境变量使用 `@group` ,设置一组单元的开启和关闭。
没有设置 `group` 字段的单元,默认组名为 `default`
## 快速退出
默认情况下,即便是没有 L3 类型任务 (`daemon`, `cron`, `logrotate` 等),`minit` 也会持续运行,以支撑起容器主进程。
如果要在 `initContainers` 中,或者容器外使用 `minit`,可以将环境变量 `MINIT_QUICK_EXIT` 设置为 `true`
此时,如果没有 L3 类型任务,`minit` 会自动退出
## 资源限制 (ulimit)
**注意,使用此功能可能需要容器运行在高权限 (Privileged) 模式**
使用环境变量 `MINIT_RLIMIT_XXXX` 来设置容器的资源限制,`unlimited` 代表无限制, `-` 表示不修改
比如:
```
MINIT_RLIMIT_NOFILE=unlimited # 同时设置软硬限制为 unlimited
MINIT_RLIMIT_NOFILE=128:unlimited # 设置软限制为 128,设置硬限制为 unlimited
MINIT_RLIMIT_NOFILE=128:- # 设置软限制为 128,硬限制不变
MINIT_RLIMIT_NOFILE=-:unlimited # 软限制不变,硬限制修改为 unlimited
```
可用的环境变量有:
```
MINIT_RLIMIT_AS
MINIT_RLIMIT_CORE
MINIT_RLIMIT_CPU
MINIT_RLIMIT_DATA
MINIT_RLIMIT_FSIZE
MINIT_RLIMIT_LOCKS
MINIT_RLIMIT_MEMLOCK
MINIT_RLIMIT_MSGQUEUE
MINIT_RLIMIT_NICE
MINIT_RLIMIT_NOFILE
MINIT_RLIMIT_NPROC
MINIT_RLIMIT_RTPRIO
MINIT_RLIMIT_SIGPENDING
MINIT_RLIMIT_STACK
```
## 内核参数 (sysctl)
**注意,使用此功能可能需要容器运行在高权限 (Privileged) 模式**
使用环境变量 `MINIT_SYSCTL` 来写入 `sysctl` 配置项,`minit` 会自动写入 `/proc/sys` 目录下对应的参数
使用 `,` 分隔多个值
比如:
```
MINIT_SYSCTL=vm.max_map_count=262144,vm.swappiness=60
```
## 透明大页 (THP)
**注意,使用此功能可能需要容器运行在高权限 (Privileged) 模式,并且需要挂载 /sys 目录**
使用环境变量 `MINIT_THP` 修改 透明大页配置,可选值为 `never`, `madvise` 和 `always`
## WebDAV 服务
我懂你的痛,当你在容器里面生成了一份调试信息,比如 `Arthas` 或者 `Go pprof` 的火焰图,然后你开始绞尽脑汁想办法把这个文件传输出来
现在,不再需要这份痛苦了,`minit` 内置 `WebDAV` 服务,你可以像暴露一个标准服务一样暴露出来,省去了调度主机+映射主机目录等一堆烦心事
环境变量:
* `MINIT_WEBDAV_ROOT` 指定要暴露的路径并启动 WebDAV 服务,比如 `/srv`
* `MINIT_WEBDAV_PORT` 指定 `WebDAV` 服务的端口,默认为 `7486`
* `MINIT_WEBDAV_USERNAME` 和 `MINIT_WEBDAV_PASSWORD` 指定 `WebDAV` 服务的用户密码,默认不设置用户密码
## 许可证
Guo Y.K., MIT License
105 changes: 105 additions & 0 deletions execute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package main

import (
"fmt"
"github.com/acicn/minit/pkg/mlog"
"github.com/acicn/minit/pkg/shellquote"
"io"
"os"
"os/exec"
"strings"
"sync"
)

var (
childPids = map[int]bool{}
childPidsLock sync.Locker = &sync.Mutex{}
)

type ExecuteOptions struct {
Dir string `yaml:"dir"` // 所有涉及命令执行的单元,指定命令执行时的当前目录
Shell string `yaml:"shell"` // 使用 shell 来执行命令,比如 'bash'
Command []string `yaml:"command"` // 所有涉及命令执行的单元,指定命令执行的内容
}

func addPid(pid int) {
childPidsLock.Lock()
defer childPidsLock.Unlock()
childPids[pid] = true
}

func removePid(pid int) {
childPidsLock.Lock()
defer childPidsLock.Unlock()
delete(childPids, pid)
}

func notifyPIDs(sig os.Signal) {
childPidsLock.Lock()
defer childPidsLock.Unlock()
for pid, found := range childPids {
if found {
if process, _ := os.FindProcess(pid); process != nil {
_ = process.Signal(sig)
}
}
}
}

func execute(opts ExecuteOptions, logger *mlog.Logger) (err error) {
argv := make([]string, 0)

// 构建 argv
if opts.Shell != "" {
if argv, err = shellquote.Split(opts.Shell); err != nil {
err = fmt.Errorf("无法处理 shell 参数,请检查: %s", err.Error())
return
}
} else {
for _, arg := range opts.Command {
argv = append(argv, os.ExpandEnv(arg))
}
}

// 构建 cmd
var outPipe, errPipe io.ReadCloser
cmd := exec.Command(argv[0], argv[1:]...)
if opts.Shell != "" {
cmd.Stdin = strings.NewReader(strings.Join(opts.Command, "\n"))
}
cmd.Dir = opts.Dir
// 阻止信号传递
setupCmdSysProcAttr(cmd)

if outPipe, err = cmd.StdoutPipe(); err != nil {
return
}
if errPipe, err = cmd.StderrPipe(); err != nil {
return
}

// 执行
if err = cmd.Start(); err != nil {
return
}

// 记录 Pid
addPid(cmd.Process.Pid)

// 串流
go logger.StreamOut(outPipe)
go logger.StreamErr(errPipe)

// 等待退出
if err = cmd.Wait(); err != nil {
logger.Errorf("进程退出: %s", err.Error())
err = nil
} else {
logger.Printf("进程退出")
}

// 移除 Pid
removePid(cmd.Process.Pid)

return
}
Loading

0 comments on commit 5efc92f

Please sign in to comment.