Drone不能感应代码托管的event触发问题的研究与探索

问题描述

在探索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

这样就可以下载golang.org/x的Golang包了。

最后

在做容器化的时候,还是需要把mount的权限控制好,不能随便地mount宿主机上面的sock,目录等一系列的敏感文件到docker容器里面,要不然一不小心就会把宿主机上面的东西破坏掉,而且是自己人。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值