@gzm1997
2018-11-08T15:34:23.000000Z
字数 3752
阅读 1770
docker
cayley
郭柱明
cayley是一个用go语言编写的很规范的图数据库 他的设计方式很值得我们学习 这篇文章我们简单聊一聊docker在cayley这个图数据库中的应用
docker主要在cayley中有两处使用
第一点很常见 就是使用dockerfile的方式将应用打包成镜像 这样我们可以通过docker很简单地安装和运行cayley
但是第二点真的很少见 cayley在第二点上算是非常创新的做法 现在主要讨论第二点
docker的使用情景有很多种 我们可以看一下阮一峰博客上说的
cayley在这里正是第一种情况 使用docker提供一次性环境
因为cayley是支持多种数据库作为底层存储 如果在开发测试过程 对每一种数据都进行本地安装 本地系统环境等因素一定会让操作很麻烦 所以cayley选择在各种测试进行的时候使用docker镜像生成一个需要的数据库容器 并且将这个容器运行起来 返回这个容器运行在的IP地址和端口 这样就可以提供给测试的代码进行连接 注意整个测试并不是发生在容器内部的 仅仅是数据库在容器内部
当我们操作完需要的持久层操作之后 就可以彻底删除这个容器 也不会导致测试的数据遗留
如果现在不熟悉docker的基本使用 可以先看一下阮一峰这篇博客先了解简单的docker helloworld
cayley使用一个封装了docker remote api的第三方包go docker client生成一个数据库容器的步骤如下
注意 cayley是原本缺失第二步pullimage的 所以导致原来运行cayley所有的测试都会导致设计持久层的测试被skip 正就是我帮cayley修复那个bug 扬哥因此送了我一瓶红酒 commit详情在pull image from remote repository if there is not image at local machine 这是我人生第一次给比较大的开源项目贡献代码。。
下面是我看了cayley的代码后 整理的一个hello world 生成一个可用的mongo的容器 运行 并且返回mongo的IP地址和端口进行连接 因为下面我已经写了尽可能多的注释了 所以不一一解释了
package main
import (
"github.com/fsouza/go-dockerclient"
"time"
"log"
"fmt"
"bytes"
"runtime"
"strconv"
"net"
"math/rand"
)
//配置
type fullConfig struct {
//主要是镜像的配置
docker.Config
//主要是容器的配置
docker.HostConfig
}
//检测IP地址和端口是否可用 可连接
const wait = time.Second * 5
func waitPort(addr string) bool {
start := time.Now()
c, err := net.DialTimeout("tcp", addr, wait)
if err == nil {
c.Close()
} else if dt := time.Since(start); dt < wait {
time.Sleep(wait - dt)
}
return err == nil
}
//随机选择可用的端口
const localhost = "127.0.0.1"
func randPort() int {
const (
min = 10000
max = 30000
)
for {
port := min + rand.Intn(max-min)
c, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", localhost, port), time.Second)
if c != nil {
c.Close()
}
if err != nil {
// TODO: check for a specific error
return port
}
}
}
func main() {
//一般通过以下sock文件初始化docker client
Address := `unix:///var/run/docker.sock`
//初始化镜像和容器的配置
var conf docker.Config
port := "27017"
//镜像的名字叫mongo
conf.Image = "mongo"
conf.OpenStdin = true
conf.Tty = true
//容器暴露出27017这个端口
conf.ExposedPorts = map[docker.Port]struct{} {
"27017/tcp": {},
}
fconf := fullConfig{
Config: conf,
HostConfig: docker.HostConfig{
//端口映射 容器的27017端口映射到本地的27017端口
PortBindings: map[docker.Port][]docker.PortBinding{
"27017/tcp": {
{
HostIP: "0.0.0.0",
HostPort: port,
},
},
},
},
}
//在linux系统下 可以原生地运行docker 但是在其他系统下需要特殊处理 随机选择可用的端口进行映射
if runtime.GOOS != "linux" {
log.Println("this is not linux")
lport := strconv.Itoa(randPort())
// nothing except Linux runs Docker natively,
// so we randomize the port and expose it on Docker VM
fconf.PortBindings = map[docker.Port][]docker.PortBinding{
docker.Port(port + "/tcp"): {{
HostIP: localhost,
HostPort: lport,
}},
}
port = lport
log.Println("this is not linux env, change the port to", lport)
}
//初始化docker client
cli, err := docker.NewClient(Address)
if err != nil {
panic(err)
}
//从远程拉取指定的docker镜像 如果本地已经存在指定镜像 那么操作被跳过
var buf bytes.Buffer
if err := cli.PullImage(docker.PullImageOptions{
//通过 镜像所在组/镜像名称:镜像标签 来指定特定的镜像
Repository: "docker.io/mongo:latest",
//指定输出位置
OutputStream: &buf,
}, docker.AuthConfiguration{}); err != nil {
log.Println("pull 容器失败")
panic(err)
}
log.Println("buf", buf.String())
//从上面的镜像中生成一个docker容器
cont, err := cli.CreateContainer(docker.CreateContainerOptions{
Config: &fconf.Config,
HostConfig: &fconf.HostConfig,
})
if err != nil {
log.Println("创建容器失败")
panic(err)
}
//生成一个强制移除容器的关闭函数
closer := func() {
cli.RemoveContainer(docker.RemoveContainerOptions{
ID: cont.ID,
Force: true,
})
}
defer closer()
//启动容器
if err := cli.StartContainer(cont.ID, &fconf.HostConfig); err != nil {
log.Println("启动容器失败")
closer()
panic(err)
}
//监听容器是否启动成功
info, err := cli.InspectContainer(cont.ID)
if err != nil {
log.Println("监视容器失败")
closer()
panic(err)
}
//获取容器运行在的IP地址和端口 这里默认使用的是 bridge的网络模式
addr := info.NetworkSettings.IPAddress
addr += ":" + port
//在10机会监听数据库的连接url是否可用
ok := false
for i := 0; i < 10 && !ok; i++ {
ok = waitPort(addr)
if !ok {
time.Sleep(time.Second * 2)
}
}
if !ok {
log.Println("一直连接失败")
closer()
log.Fatal("tcp connect fail")
}
fmt.Println("addr", addr)
}
cayley将上面的常用操作都封装成函数在docker操作 这些使用docker生成一次性数据库并使用的一个例子可见mongo_test
cayley在这一点上做的很精彩 也很创新 非常值得我们学习