问题描述
在探索Drone的使用的时候,发现在本机上面启动Drone的server端,再在GitLab上面授权应用,并且把项目active之后,最后往GitLab推送代码的时候,没有触发自动构建过程。
问题根源
排查下来是因为触发事件之后,GitLab的确使用Webhook往配置的Drone-server发送了一个消息,但是这个相当于外网访问内网机器,这个是做不到的。
解决方法
现在想到的方法有:通过花生壳之类的软件来获取机器的公网域名,通过NAT把Webhook内容转发到相关的机器上面。
更多的探索
找到相关的发送请求的语句
通过查阅docker的官方文档里面client的README.md,看到下面的代码:
传送门
package main
import (
"context"
"fmt"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
)
func main() {
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
panic(err)
}
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
if err != nil {
panic(err)
}
for _, container := range containers {
fmt.Printf("%s %s\n", container.ID[:10], container.Image)
}
}
使用上面的ContainerList就可以获取本机所有容器的列表。
func (cli *Client) ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) {
query := url.Values{}
if options.All {
query.Set("all", "1")
}
if options.Limit != -1 {
query.Set("limit", strconv.Itoa(options.Limit))
}
if options.Since != "" {
query.Set("since", options.Since)
}
if options.Before != "" {
query.Set("before", options.Before)
}
if options.Size {
query.Set("size", "1")
}
if options.Filters.Len() > 0 {
filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters)
if err != nil {
return nil, err
}
query.Set("filters", filterJSON)
}
resp, err := cli.get(ctx, "/containers/json", query, nil)
if err != nil {
return nil, err
}
var containers []types.Container
err = json.NewDecoder(resp.body).Decode(&containers)
ensureReaderClosed(resp)
return containers, err
}
里面最主要的发请求的语句就是:
resp, err := cli.get(ctx, “/containers/json”, query, nil)
相关的方法调用顺序为:
func (cli *Client) get(ctx context.Context, path string, query url.Values, headers map[string][]string) (serverResponse, error) {}
func (cli *Client) sendRequest(ctx context.Context, method, path string, query url.Values, body io.Reader, headers headers) (serverResponse, error) {}
func (cli *Client) doRequest(ctx context.Context, req *http.Request) (serverResponse, error) {}
在最后一个doRequest方法里面就是发送请求的核心语句:
resp, err := cli.client.Do(req)
找到client数据结构
type Client struct {
// scheme sets the scheme for the client
scheme string
// host holds the server address to connect to
host string
// proto holds the client protocol i.e. unix.
proto string
// addr holds the client address.
addr string
// basePath holds the path to prepend to the requests.
basePath string
// client used to send and receive http requests.
client *http.Client
// version of the server to talk to.
version string
// custom http headers configured by users.
customHTTPHeaders map[string]string
// manualOverride is set to true when the version was set by users.
manualOverride bool
}
从上面就可以看到核心的client数据结构里面的client是一个http.Client,所以Do方法就是发送HTTP请求,但是往哪个套接字里面发送请求呢?
找到docker-server的监听套接字
看到创建Client对象的时候调用的函数:
func NewClientWithOpts(ops ...func(*Client) error) (*Client, error) {
client, err := defaultHTTPClient(DefaultDockerHost)
if err != nil {
return nil, err
}
c := &Client{
host: DefaultDockerHost,
version: api.DefaultVersion,
client: client,
proto: defaultProto,
addr: defaultAddr,
}
for _, op := range ops {
if err := op(c); err != nil {
return nil, err
}
}
if _, ok := c.client.Transport.(http.RoundTripper); !ok {
return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", c.client.Transport)
}
if c.scheme == "" {
c.scheme = "http"
tlsConfig := resolveTLSConfig(c.client.Transport)
if tlsConfig != nil {
c.scheme = "https"
}
}
return c, nil
}
可以看到Client的host是DefaultDockerHost,是client包里面的一个全局变量,可以全局搜一下:
const DefaultDockerHost = "unix:///var/run/docker.sock"
嗯,可以看到,终于找到最后的套接字了。就是/var/run/docker.sock
docker 客户端就是往这个套接字发送消息来控制本机的docker 服务端,包括ps,kill等命令。
更多骚操作
我们可以把这个套接字mount到镜像里面
Dockerfile:
mount : /var/run/docker.sock:/var/run/docker.sock
假如我们在一个发布环境里面,我们就可以在想要发布的服务的Dockerfile文件里面加上上面一句,然后发布这个服务,进入这个已经发布的容器,编写一个Go文件,文件的内容就是官方文档上面的示例[开篇的第一段代码],然后我们就可以从发布的容器内去看到宿主机上面的所有容器了,包括restart,remove,kill等一系列的危险操作。
慎用
慎用
慎用
可能会遇到的问题
上面的Go文件可能会报错,因为API的版本不兼容
解决方法是:
我们可以看到api/common.go 这个文件里面会有API的版本,然后我们git log common.go这个文件,再找到匹配的版本之后,直接git checkout那个版本出来,就可以解决这个问题。
针对于docker版本实在太低的话,那就要缺什么补什么了,一般在git上面的Go包下载都是很快的,但是有一个例外:就是golang.org/x/*的包,这个就麻烦了,直接go get 会timeout。
这个其实有解决的方法:
mkdir -p $GOPATH/src/golang.org/x
cd $GOPATH/src/golang.org/x
git clone https://github.com/golang/net.git
最后
在做容器化的时候,还是需要把mount的权限控制好,不能随便地mount宿主机上面的sock,目录等一系列的敏感文件到docker容器里面,要不然一不小心就会把宿主机上面的东西破坏掉,而且是自己人。