- 用了golang一阵子,然后自己琢磨着尝试写了个分布式的游戏服务器。
突然想到要把它部署到docker上,网上查看了别人的一些经验,发现大部分都只提到简单的将单个golang文件main.go添加到docker上,然后运行后完事了没了。
然后,遗留了一些问题没有我没理解,如,docker里依赖的第三方库找不到如何解决,分布式多个子服务器的Dockerfile如何解决等。于是和同行们一些简单交流和翻看了一些官方文档,得到了一个大体方案。
叙述三个问题:
一,在Docker运行golangTCP服务监听,并成功访问
二,减小镜像包体
三,依赖的第三方代码和分布式构建镜像
假设项目内有两个子服务,client和server,则有项目结构:
client/main.go:
package main
import(
"os"
"net"
"fmt"
)
//模拟客户端
func main() {
//要从docker里访问宿主机器的端口,那么主机地址应该是局域网内分配的地址,
//这里我的机器的分配地址是192.168.0.3
//不能是127.0.0.1或0.0.0.0
addr := "192.168.0.3:80"
tcpaddr, _ := net.ResolveTCPAddr("tcp4", addr)
_, err := net.DialTCP("tcp4", nil, tcpaddr)
if err == nil {
fmt.Println(addr+"请求成功!")
}else{
fmt.Println(addr+"请求失败!")
}
os.Exit(0)
}
server/main.go
package main
import(
"net"
"fmt"
)
func main() {
addr,_:=net.ResolveTCPAddr("tcp4", ":80")
ls,_:=net.ListenTCP("tcp", addr)
for{
conn, err := ls.Accept()
if err != nil{
fmt.Println(err)
continue
}
go handle(conn)
}
}
func handle(conn net.Conn){
defer conn.Close()
fmt.Println("监听到一个TCP请求连接!")
}
一,在Docker运行golangTCP服务监听,并成功访问
从简入手,首先单个server/main.go文件在docker上运行:
有server/Dockerfile :
# 使用最新版 golang 作为基础镜像
FROM golang:latest
#设置工作目录,没有则自动新建
WORKDIR /go/src/app/
#拷贝代码到当前
COPY main.go .
#golang镜像中的go编译命令
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server .
#运行服务
CMD ["./server"]
在命令行终端cd到server/目录下,执行编译命令
docker build -t go_server .
编译正常过程log:
Sending build context to Docker daemon 3.072kB
Step 1/5 : FROM golang:latest
---> 7ced090ee82e
Step 2/5 : WORKDIR /go/src/app/
---> Using cache
---> f1d241ee87fe
Step 3/5 : COPY main.go .
---> bb3313eea376
Step 4/5 : RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server .
---> Running in 95b2c25f1782
Removing intermediate container 95b2c25f1782
---> 29295a4bf0ac
Step 5/5 : CMD ["./server"]
---> Running in 1a074496c162
Removing intermediate container 1a074496c162
---> 8999925901e6
Successfully built 8999925901e6
Successfully tagged go_server:latest
执行查看镜像命令:
docker images
结果看到了生成的go_server镜像。
启动go_server为容器:
将docker里go_server容器80端口暴露到宿主机器的80端口。
docker run -p 80:80 go_server
新开一个命令终端,cd到clinet/下直接运行client/main.go代码请求docker里的服务器。
docker go_server这边则看到log
成功!
二,减小镜像包体
以上一节内容可以看到,镜像包有700+m,很大,所以要减小镜像包的大小,方案就是基于golang镜像用alpine来构建。
官网参考:https://docs.docker.com/develop/develop-images/multistage-build/ server/Dockerfile文件改为:
# 使用最新版 golang 作为基础镜像
FROM golang:latest AS builder
#设置工作目录,没有则自动新建
WORKDIR /go/src/app/
#拷贝代码到当前
COPY main.go .
#golang镜像中的go编译命令
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server .
#基于builder构建go_server
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/app/ .
#运行服务
CMD ["./server"]
结果看到新的go_server只有8.97m,运行正常!
三,依赖的第三方代码和分布式构建镜像
首先打开/server/main.go代码文件,
添加一行代码,对第三方库protobuf的引用:
_“github.com/golang/protobuf/proto”
package main
import(
"net"
"fmt"
_"github.com/golang/protobuf/proto"
)
...
进行docker build命令,发现会报错,找不到protobuf库。这也合理,毕竟构建这个镜像没有添加第三方库的代码。
如果要结合docker引用第三方库,那么就要将第三方代码也复制到docker构建的镜像里。
所以这里用到了一个golang依赖包管理工具dep:https://github.com/golang/dep 这个dep的作用主要是将自己依赖GOPATH的第三方库copy到自己的目录下,仅仅供自己项目使用,不再去GOPATH/GOROOT查找第三方库,可避免同一台电脑有两个项目时依赖GOPATH上同一个库的不同版本时要来回切换设置GOPATH。
如此一来,一个项目下有完自己所依赖的代码,那么就可以将整个项目COPY到docker构建的镜像里运行了。
PS:Dockerfile仅仅能识别出宿主机器的Dockerfile根目录下的文件和文件夹,无法识别宿主机器Dockerfile根目录外的文件和文件夹,所以要置放第三方库到Dockerfile目录下才能COPY到镜像里,而dep正好能做到这一点。
安装:
MacOS:
$ brew install dep
$ brew upgrade dep
Debian:
$ sudo apt-get install go-dep
Linux :
$ curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
Win:下载后放到gobin下,
https://bin.equinox.io/a/59wHzG494MG/github-com-golang-dep-cmd-dep-windows-amd64.zip 或:
go get -u github.com/golang/dep/cmd/dep
输入dep,显示如下安装成功!
来到项目server和client文件夹的上一级根目录,执行
dep init
或者联网慢的话
deb init -v
然后看到多出了三项文件夹/文件,vendor就是存放当前引用库代码的文件夹。之后项目引用的第三方库都会优先在vendor文件夹下寻找。client微服和server微服的依赖的第三方库都在里面。
命令dep status可以查看到它们的版本:
所以有思路:
1,现在client和server文件夹的上一级根目录下执行dep init,将所有子服务器代码依赖的第三方库统一置于项目下。当前目录新建Dockerfile,基于golang镜像构建一个goang_dep镜像,仅仅包含所有依赖的第三方库,作为子服务器的基类镜像。
2,进入到子服务器文件夹根目录,如server/下,修改Dockerfile为基于步骤1构建的golang_dep镜像构建go_server镜像,那么go_server依赖的第三方库将在golang_dep里找到。
以server举例,开始,在clinet和server文件夹的上一级目录新建Dockerfile,
内容:
#基础包独立出来,主要包括了第三方库
#后面的clinet和server将基于此包构建
FROM golang:latest AS golang_dep
#/go/src为golang的默认GOPATH
WORKDIR /go/src
#将dep相关内容COPY到/go/src
COPY ./vendor ./vendor
COPY Gopkg.lock .
COPY Gopkg.toml .
项目根目录下执行:
docker build -t goalng_dep .
得到golang_dep镜像:
然后进入到server文件夹,打开server/Dockerfile修改为如下,基于上个golang_dep镜像构建:
#继承自golang_dep构建,
FROM golang_dep AS server_base
WORKDIR /go/src/server
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server .
FROM alpine:latest AS go_server
RUN apk --no-cache add ca-certificates
WORKDIR /app/server/
COPY --from=server_base /go/src/ .
CMD ["./server/server"]
server文件夹下执行:
docker build -t go_server .
不再报错找不到protobuf库,得到go_server镜像,运行没问题: