前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >mac下开启docker API远程调用

mac下开启docker API远程调用

原创
作者头像
暮雨
修改2018-10-23 11:13:11
6.2K2
修改2018-10-23 11:13:11
举报
文章被收录于专栏:云端漫步云端漫步

docker容器技术至今已有五年的发展,作为一个工具,已经像Linux命令一样融入我们开发的生活。现在大多开发都使用Mac作为开发机,大都会装一个Docker for mac这个Mac下的docker工具。本文将从以下几个话题进行展开,说明Mac下docker的使用原理。

Docker for mac介绍

docker for mac 是docker在Mac机器上的一个docker工具集。集成了Kitematic, Docker图形用户界面,现在又集成了kubernetes。对于开发人员是很方便使用的,安装也很简单,教程也很多。

docker是基于Linux 容器技术开发出来的,Mac OS是unix系统,按照该理论,Mac下是不能运行容器的。之前自己的Mac下跑一个namespace隔离的demo实验,跑完后,一直无法得到理论中的结果。突然才意识到是操作系统环境的问题,Mac OS下也许不支持这种namespace隔离导致的,随后,就倒腾了一台安装ubuntu18.04的虚拟机,跑出了希望的结果。

通过这个例子说明,Mac OS下运行的docker不是原生的docker。容器的daemon应该运行在一个Linux环境虚拟机中。这里只做一个猜想,因为还没找到资料证明认为的这个结论。我们知道,在单机环境中,docker的客户端和daemon的通信是默认方式是socket文件的方式。在Linux环境中,我们要使用docker server端提供的API,就需要设置DOCKER_OPTS= -H tcp://0.0.0.0:2375 server端监听2375端口,我们才可以通过docker 提供的API进行接口调用。

那么Mac OS环境的文件系统找了好久,也没找到docker server端提供的参数配置文件。然后Google后找到了点思路。

Docker for Mac提供的UI界面可以配置该参数。

尝试添加tcp监听端口,结果如下所示。那么就需探索其它方法。

在stackoverflow中看到一个回答,进入docker daemon所在的虚拟机中

代码语言:javascript
复制
screen ~/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/tty
linuxkit-025000000001:~# ps -ef | grep docker
 1003 root      0:03 containerd-shim -namespace services.linuxkit -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/services.linuxkit/docker-ce -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd
 1025 root      0:00 /usr/local/bin/docker-init /usr/bin/entrypoint.sh
 1176 root      0:00 {start-docker.sh} /bin/sh /usr/bin/start-docker.sh
 1184 root      0:00 /usr/sbin/sntpc -v -i 30 gateway.docker.internal
 1226 root      1:12 kubelet --kubeconfig=/etc/kubernetes/kubelet.conf --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --pod-manifest-path=/etc/kubernetes/manifests --allow-privileged=true --cluster-dns=10.96.0.10 --cluster-domain=cluster.local --cgroups-per-qos=false --enforce-node-allocatable= --network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin --cadvisor-port=0 --kube-reserved-cgroup=podruntime --system-reserved-cgroup=systemreserved --cgroup-root=kubepods --hostname-override=docker-for-desktop --fail-swap-on=false
 1275 root      0:24 /usr/local/bin/dockerd -H unix:///var/run/docker.sock --config-file /run/config/docker/daemon.json --swarm-default-advertise-addr=eth0 --userland-proxy-path /usr/bin/V**kit-expose-port
 1318 root      0:00 /usr/bin/trim-after-delete -- /sbin/fstrim /var/lib/docker
 1422 root      0:00 /vsudd -inport 2376:unix:/var/run/docker.sock
 1474 root      0:07 docker-containerd --config /var/run/docker/containerd/containerd.toml
linuxkit-025000000001:~# more /run/config/docker/daemon.json
{
  "debug" : true,
  "experimental" : true
}

说明UI管理的daemon的配置数据是从这里获得。通过以上的实验,我们可以知道,在验证文章开头的那个猜想是正确的,Mac OS下的docker是通过Linux虚拟机的方式运行的。这里看到了linuxkit,这又是一项什么技术,先收一收吧,担心自己还没法驾驭的了。

Docker for mac 的知识点展开就先到这里了。我们的问题是怎么解决Mac OS下docker API的使用问题,目前还没有解决。还需要探索新的东西来解决该问题。

socket通信和http协议在进程间通信

在Linuxkit下的命令行中,我们看到了dockerd -H unix:///var/run/docker.sock,这是docker daemon的默认通信方式,那么我们是否可以将tcp协议转化为socket协议,访问外国网站软件大都基于这样的一种协议转化。在此,还不是十分的清楚明白这种sock文件通信的机制。

来补一补这一节理论知识。

"unix://"这种通信协议被称为UNIX域协议,它主要用于同一主机下,不同进程间的通信,比tcp协议的通信效率高很多。通socket编程又有不同。

unix域提供两类套接字:字节流套接字(类似TCP)和数据报套接字(类似UDP)。使用unix域协议有如下的优势: (1)unix域套接字往往比通信两端位于同一个主机的TCP套接字快出一倍。 (2)unix域套接字可用于在同一个主机上的不同进程之间传递描述符。 (3)unix域套接字较新的实现把客户的凭证(用户ID和组ID)提供给服务器,从而能够提供额外的安全检查措施。

unix域中用于标识客户和服务器的协议地址是普通文件系统中的路径名

来看一个go实现的demo

服务端:server.go

代码语言:javascript
复制
package main
 
import (
	"encoding/binary"
	"bytes"
	"io"
	"os"
	"fmt"
	"net"
	"time"
)
 
const (
	UNIX_SOCK_PIPE_PATH = "/var/run/unixsock_test.sock" // socket file path
)
 
func main() {
	// Remove socket file
	os.Remove(UNIX_SOCK_PIPE_PATH)
	// Get unix socket address based on file path
	uaddr, err := net.ResolveUnixAddr("unix", UNIX_SOCK_PIPE_PATH)
	if err != nil {
		fmt.Println(err)
		return
	}
 
	// Listen on the address
	unixListener, err := net.ListenUnix("unix", uaddr)
	if err != nil {
		fmt.Println(err)
		return
	}
 
	// Close listener when close this function, you can also emit it because this function will not terminate gracefully
	defer unixListener.Close()
 
	fmt.Println("Waiting for asking questions ...")
	// Monitor request and process
	for {
		uconn, err := unixListener.AcceptUnix()
		if err != nil {
			fmt.Println(err)
			continue
		}
 
		// Handle request
		go handleConnection(uconn)
	}
}
 
/*******************************************************
* Handle connection and request
* conn: conn handler
*******************************************************/
func handleConnection(conn *net.UnixConn) {
	// Close connection when finish handling
	defer func() {
		conn.Close()
	}()
 
	// Read data and return response
	data, err := parseRequest(conn)
	if err != nil {
		fmt.Println(err)
		return
	}
 
	fmt.Printf("%+v\tReceived from client: %s\n", time.Now(), string(data))
	time.Sleep(time.Duration(1) * time.Second) // sleep to simulate request process
 
	// Send back response
	sendResponse(conn, []byte(time.Now().String()))
}
 
/*******************************************************
* Parse request of unix socket
* conn: conn handler
*******************************************************/
func parseRequest(conn *net.UnixConn) ([]byte, error) {
	var reqLen uint32
	lenBytes := make([]byte, 4)
	if _, err := io.ReadFull(conn, lenBytes); err != nil {
		return nil, err
	}
 
	lenBuf := bytes.NewBuffer(lenBytes)
	if err := binary.Read(lenBuf, binary.BigEndian, &reqLen); err != nil {
		return nil, err
	}
 
	reqBytes := make([]byte, reqLen)
	_, err := io.ReadFull(conn, reqBytes)
 
	if err != nil {
		return nil, err
	}
 
	return reqBytes, nil
}
 
/*******************************************************
* Send response to client
* conn: conn handler
*******************************************************/
func sendResponse(conn *net.UnixConn, data []byte) {
	buf := new(bytes.Buffer)
	msglen := uint32(len(data))
 
	binary.Write(buf, binary.BigEndian, &msglen)
	data = append(buf.Bytes(), data...)
 
	conn.Write(data)
}

客户端:client.go

代码语言:javascript
复制
package main
 
import (
	"time"
	"io"
	"encoding/binary"
	"bytes"
	"fmt"
	"net"
)
 
const (
	UNIX_SOCK_PIPE_PATH = "/var/run/unixsock_test.sock" // socket file path
)
 
var (
	exitSemaphore chan bool
)
 
func main() {
	// Get unix socket address based on file path
	uaddr, err := net.ResolveUnixAddr("unix", UNIX_SOCK_PIPE_PATH)
	if err != nil {
		fmt.Println(err)
		return
	}
 
	// Connect server with unix socket
	uconn, err := net.DialUnix("unix", nil, uaddr)
	if err != nil {
		fmt.Println(err)
		return
	}
 
	// Close unix socket when exit this function
	defer uconn.Close()
	
	// Wait to receive response
	go onMessageReceived(uconn)
 
	// Send a request to server
	// you can define your own rules
	msg := "tell me current time\n"
	sendRequest(uconn, []byte(msg))
 
	// Wait server response
	// change this duration bigger than server sleep time to get correct response
	exitSemaphore = make(chan bool)
	select {
	case <- time.After(time.Duration(2) * time.Second):
		fmt.Println("Wait response timeout")
	case <-exitSemaphore:
		fmt.Println("Get response correctly")
	}
 
	close(exitSemaphore)
}
 
/*******************************************************
* Send request to server, you can define your own proxy
* conn: conn handler
*******************************************************/
func sendRequest(conn *net.UnixConn, data []byte) {
	buf := new(bytes.Buffer)
	msglen := uint32(len(data))
 
	binary.Write(buf, binary.BigEndian, &msglen)
	data = append(buf.Bytes(), data...)
 
	conn.Write(data)
}
 
/*******************************************************
* Handle connection and response
* conn: conn handler
*******************************************************/
func onMessageReceived(conn *net.UnixConn) {
	//for { // io Read will wait here, we don't need for loop to check
		// Read information from response
		data, err := parseResponse(conn)
		if err != nil {
			fmt.Println(err)
		} else {
			fmt.Printf("%v\tReceived from server: %s\n", time.Now(), string(data))
		}
 
		// Exit when receive data from server
		exitSemaphore <- true
	//}
}
 
/*******************************************************
* Parse request of unix socket
* conn: conn handler
*******************************************************/
func parseResponse(conn *net.UnixConn) ([]byte, error) {
	var reqLen uint32
	lenBytes := make([]byte, 4)
	if _, err := io.ReadFull(conn, lenBytes); err != nil {
		return nil, err
	}
 
	lenBuf := bytes.NewBuffer(lenBytes)
	if err := binary.Read(lenBuf, binary.BigEndian, &reqLen); err != nil {
		return nil, err
	}
 
	reqBytes := make([]byte, reqLen)
	_, err := io.ReadFull(conn, reqBytes)
 
	if err != nil {
		return nil, err
	}
 
	return reqBytes, nil
}

备注:代码参考 https://blog.csdn.net/lwc5411117/article/details/83018252

通过这个例子,大概能说明unix域协议了吧。我们要实现的目标就是将tcp转化为unix域协议,这里有一个号称网络界瑞士军刀socat可以实现我们的想法。

mac下docker API调用实现

现在问题解决的思路很清晰了,闲话不说,直接上成熟的解决方案。

通过容器的方式来解决

```

docker run -d -v /var/run/docker.sock:/var/run/docker.sock -p 2376:2375 \

bobrik/socat TCP4-LISTEN:2375,fork,reuseaddr UNIX-CONNECT:/var/run/docker.sock

```

对于以上命令的意思,在此放一个外链socat入门。

测试

代码语言:javascript
复制
 $curl -XGET http://127.0.0.1:2376/version | python -mjson.tool
 {
    "ApiVersion": "1.38",
    "Arch": "amd64",
    "BuildTime": "2018-07-18T19:13:46.000000000+00:00",
    "Components": [
        {
            "Details": {
                "ApiVersion": "1.38",
                "Arch": "amd64",
                "BuildTime": "2018-07-18T19:13:46.000000000+00:00",
                "Experimental": "true",
                "GitCommit": "0ffa825",
                "GoVersion": "go1.10.3",
                "KernelVersion": "4.9.93-linuxkit-aufs",
                "MinAPIVersion": "1.12",
                "Os": "linux"
            },
            "Name": "Engine",
            "Version": "18.06.0-ce"
        }
    ],
    "Experimental": true,
    "GitCommit": "0ffa825",
    "GoVersion": "go1.10.3",
    "KernelVersion": "4.9.93-linuxkit-aufs",
    "MinAPIVersion": "1.12",
    "Os": "linux",
    "Platform": {
        "Name": ""
    },
    "Version": "18.06.0-ce"
}

完美的实现了docker API的使用。

curl 这个工具也是可以支持unix域协议

代码语言:javascript
复制
$curl -XGET --unix-socket /var/run/docker.sock http://127.0.0.1:2375/version | python -mjson.tool
{
    "ApiVersion": "1.38",
    "Arch": "amd64",
    "BuildTime": "2018-07-18T19:13:46.000000000+00:00",
    "Components": [
        {
            "Details": {
                "ApiVersion": "1.38",
                "Arch": "amd64",
                "BuildTime": "2018-07-18T19:13:46.000000000+00:00",
                "Experimental": "true",
                "GitCommit": "0ffa825",
                "GoVersion": "go1.10.3",
                "KernelVersion": "4.9.93-linuxkit-aufs",
                "MinAPIVersion": "1.12",
                "Os": "linux"
            },
            "Name": "Engine",
            "Version": "18.06.0-ce"
        }
    ],
    "Experimental": true,
    "GitCommit": "0ffa825",
    "GoVersion": "go1.10.3",
    "KernelVersion": "4.9.93-linuxkit-aufs",
    "MinAPIVersion": "1.12",
    "Os": "linux",
    "Platform": {
        "Name": ""
    },
    "Version": "18.06.0-ce"
}

总结

到此,我们的问题已经完美的解决。对Mac 的docker原理展开做了一个说明,同时对unix域协议通过go语言实现了一个demo,最后通过socat这个强大的网络工具通过docker安装的方式解决了docker API访问的问题。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Docker for mac介绍
  • socket通信和http协议在进程间通信
  • mac下docker API调用实现
  • 总结
相关产品与服务
容器镜像服务
容器镜像服务(Tencent Container Registry,TCR)为您提供安全独享、高性能的容器镜像托管分发服务。您可同时在全球多个地域创建独享实例,以实现容器镜像的就近拉取,降低拉取时间,节约带宽成本。TCR 提供细颗粒度的权限管理及访问控制,保障您的数据安全。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档