将Golang应用部署到Docker
项目地址:https : //github.com/EDDYCJY/go-gin-example
涉及知识点
- Go + Docker
本文目标
将我们的go-gin-example应用部署到一个Docker里,你需要先准备好如下东西:
- 你需要安装好
docker。 - 如果上外网比较吃力,需要配好多重源。
码头工人
在这里简单介绍下Docker,建议深入学习

Docker是一个开源的轻量级容器技术,让开发者可以打包他们的应用以及应用运行的上下文环境到一个可移植的替代中,然后发布到任何支持Docker的系统上运行。通过容器技术,在几乎没有性能开销的情况下,Docker为应用提供了一个隔离运行环境
- 简化配置
- 代码流水线管理
- 提高开发效率
- 隔离应用
- 快速,持续部署
接下来我们正式开始对项目进行docker的所需处理和编写,每一个大标题为步骤大纲
高朗
一,编写Dockerfile
在go-gin-example项目根目录创建Dockerfile文件,写入内容
FROM golang:latest
ENV GOPROXY https://goproxy.cn,directWORKDIR $GOPATH/src/github.com/EDDYCJY/go-gin-exampleCOPY . $GOPATH/src/github.com/EDDYCJY/go-gin-exampleRUN go build .
EXPOSE 8000ENTRYPOINT ["./go-gin-example"]作用
golang:latest既是基础基础,将工作目录设置为$GOPATH/src/go-gin-example,又是内部目录的内容复制到$GOPATH/src/go-gin-example中
在进行go build编译完毕后,将容器启动程序设置为./go-gin-example,也就是我们所编译的替换文件
注意go-gin-example在docker容器里编译,并没有在宿主机现场编译
说明
Dockerfile文件是用于Docker生成过程的配置文件,文件内容是一条条指令,每一条指令包含一层,因此每一条指令的内容,就是描述该层合理的方式;这些指令基础基础并存最终创造一个新的可能性
你可以认为有用快速创建自定义的Docker更大
1,来自
指定基础规范(必须有的指令,并且必须是第一条指令)
2,WORKDIR
格式为WORKDIR<工作目录路径>
使用WORKDIR指令可以来指定工作目录(或称为当前目录),以后各层的当前目录就被替换指定的目录,如果目录不存在,WORKDIR会帮你建立目录
3,复制
格式:
COPY <源路径>... <目标路径>COPY ["<源路径1>",... "<目标路径>"]COPY指令编写合并目录中的<源路径>的文件/目录复制到新的一层的附属内部的<目标路径>位置
4,运行
使用执行命令行命令
格式:RUN<命令>
5,曝光
格式为EXPOSE<端口1> [<端口2> …]
EXPOSE指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务
在Dockerfile中写入这样的声明有两个好处
- 帮助有用使用者理解这个有用的服务的守护端口,以方便配置映射
- 运行时使用随机端口映射时,也就是
docker run -P时,会自动随机映射EXPOSE的端口
6,入口点
ENTRYPOINT的格式和RUN指令格式一样,分为两种格式
-
exec格式:<ENTRYPOINT> "<CMD>" -
shell格式:ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]
ENTRYPOINT指令是指定容器启动程序及参数
二,积累成果
go-gin-example`的项目根目录下**执行** `docker build -t gin-blog-docker .该命令作用是创建/重建,-t指定名称为gin-blog-docker,.合并内容为当前某些目录
$ docker build -t gin-blog-docker .Sending build context to Docker daemon 96.39 MBStep 1/6 : FROM golang:latest ---> d632bbfe5767Step 2/6 : WORKDIR $GOPATH/src/github.com/EDDYCJY/go-gin-example ---> 56294f978c5dRemoving intermediate container e112997b995dStep 3/6 : COPY . $GOPATH/src/github.com/EDDYCJY/go-gin-example ---> 3b60960120cfRemoving intermediate container 63e310b3f60cStep 4/6 : RUN go build . ---> Running in 52648a431450go: downloading github.com/gin-gonic/gin v1.3.0go: downloading github.com/go-ini/ini v1.32.1-0.20180214101753-32e4be5f41bbgo: downloading github.com/swaggo/gin-swagger v1.0.1-0.20190110070702-0c6fcfd3c7f3... ---> 7bfbeb301feaRemoving intermediate container 52648a431450Step 5/6 : EXPOSE 8000 ---> Running in 98f5b387d1bb ---> b65bd4076c65Removing intermediate container 98f5b387d1bbStep 6/6 : ENTRYPOINT ./go-gin-example ---> Running in c4f6cdeb667b ---> d8a109c7697cRemoving intermediate container c4f6cdeb667bSuccessfully built d8a109c7697c三,验证可行性
查看所有的可能,确定刚刚整合的gin-blog-docker足够存在
$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEgin-blog-docker latest d8a109c7697c About a minute ago 946 MBdocker.io/golang latest d632bbfe5767 8 days ago 779 MB...四,创建并运行一个新容器
执行命令 docker run -p 8000:8000 gin-blog-docker
$ docker run -p 8000:8000 gin-blog-dockerdial tcp 127.0.0.1:3306: connect: connection refused[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode)
...Actual pid is 1运行成功,你以为大功告变成吗?
你想太多了,仔细看看控制台的输出了一条错误 dial tcp 127.0.0.1:3306: connect: connection refused
我们研判一下,发现是Mysql的问题,接下来第二项我们将解决这个问题
MySQL的
一,拉取合理
从Docker的公共仓库Dockerhub下载MySQL数量(国内建议配个单位)
$ docker pull mysql二,创建并运行一个新容器
运行Mysql容器,并设置执行成功后返回容器ID
$ docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=rootroot -d mysql8c86ac986da4922492934b6fe074254c9165b8ee3e184d29865921b0fef29e64连接MySQL
初始化的Mysql应该遵循

Golang + MySQL
一,删除总计
由于原本的可能存在问题,我们需要删除它,此处有几种做法
- 删除原本有问题的合并,重新合并一个新的合并
- 重新打造一个不同
name,tag的新替代
删除原本的有问题的补充,-f是强制删除及其关联状态
若不执行-f,你需要执行docker ps -a查到所关联的容器,将其rm解除本身依赖关系
$ docker rmi -f gin-blog-dockerUntagged: gin-blog-docker:latestDeleted: sha256:d8a109c7697c3c2d9b4de7dbb49669d10106902122817b6467a031706bc52ab4Deleted: sha256:b65bd4076c65a3c24029ca4def3b3f37001ff7c9eca09e2590c4d29e1e23dce5Deleted: sha256:7bfbeb301fea9d8912a4b7c43e4bb8b69bdc57f0b416b372bfb6510e476a7deeDeleted: sha256:3b60960120cf619181c1762cdc1b8ce318b8c815e056659809252dd321bcb642Deleted: sha256:56294f978c5dfcfa4afa8ad033fd76b755b7ecb5237c6829550741a4d2ce10bc二,修改配置文件
将项目的配置文件conf/app.ini,内容修改为
#debug or releaseRUN_MODE = debug
[app]PAGE_SIZE = 10JWT_SECRET = 233
[server]HTTP_PORT = 8000READ_TIMEOUT = 60WRITE_TIMEOUT = 60
[database]TYPE = mysqlUSER = rootPASSWORD = rootrootHOST = mysql:3306NAME = blogTABLE_PREFIX = blog_三,重新建构合理
重复先前的步骤,回到gin-blog的项目根目录下执行 docker build -t gin-blog-docker .
四,创建并运行一个新容器
关联
问:我们需要将Golang容器和Mysql容器关联起来,那么我们需要怎么做呢?
A:增加命令--link mysql:mysql让Golang容器与Mysql容器互联;通过--link,可以在容器内部直接使用其关联的容器别名进行访问,而不通过IP,但是--link只能解决单机容器间的关联,在分散多机的情况下,需要通过别的方式进行连接
运行
执行命令 docker run --link mysql:mysql -p 8000:8000 gin-blog-docker
$ docker run --link mysql:mysql -p 8000:8000 gin-blog-docker[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode)...Actual pid is 1结果
检查启动输出,接口测试,数据库内部数据,均正常;我们的Golang容器和Mysql容器成功关联运行,大功告成:)
评论
思考
虽然应用已经能够跑起来了
但如果对Golang和Docker有一定的了解,我希望你能够想到至少2个问题
- 为什么
gin-blog-docker占用空间这么大?(可用docker ps -as | grep gin-blog-docker查看) Mysql容器直接这么使用,数据存储到哪里去了?
创建超小的Golang
问:第一个问题,为什么这么庞大体积这么大?
答:FROM golang:latest拉取的是官方golang认可的,包含Golang的编译和运行环境,外加一堆GCC,构建工具,相当齐全
这是有问题的,我们可以不在Golang容器中现场编译的,压根用不到那些东西,我们只需要一个能够运行替换文件的环境即可
建立从零开始
从头开始,简洁,小巧,基本是个空更长
一,修改Dockerfile
FROM scratch
WORKDIR $GOPATH/src/github.com/EDDYCJY/go-gin-exampleCOPY . $GOPATH/src/github.com/EDDYCJY/go-gin-example
EXPOSE 8000CMD ["./go-gin-example"]二,编译的文件
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o go-gin-example .在这里因为使用的是scratch副本,它是空的,因此我们需要将生成的替代文件静态链接所依赖的库
三,积累成果
$ docker build -t gin-blog-docker-scratch .Sending build context to Docker daemon 133.1 MBStep 1/5 : FROM scratch --->Step 2/5 : WORKDIR $GOPATH/src/github.com/EDDYCJY/go-gin-example ---> Using cache ---> ee07e166a638Step 3/5 : COPY . $GOPATH/src/github.com/EDDYCJY/go-gin-example ---> 1489a0693d51Removing intermediate container e3e9efc0fe4dStep 4/5 : EXPOSE 8000 ---> Running in b5630de5544a ---> 6993e9f8c944Removing intermediate container b5630de5544aStep 5/5 : CMD ./go-gin-example ---> Running in eebc0d8628ae ---> 5310bebeb86aRemoving intermediate container eebc0d8628aeSuccessfully built 5310bebeb86a注意,假设你的Golang应用没有依赖任何的配置等文件,是可以直接把插入文件给拷贝进去即可,其他都不必担心
这里可以有好几种解决方案
- 依赖文件统一管理挂载
- go-bindata一下
…
因此这里如果解决了文件依赖的问题后,就不需要把目录给COPY进去了
四,运行
$ docker run --link mysql:mysql -p 8000:8000 gin-blog-docker-scratch[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /auth --> github.com/EDDYCJY/go-gin-example/routers/api.GetAuth (3 handlers)...成功运行,程序也正常接收请求
接下来我们再看看占用大小,执行docker ps -as命令
$ docker ps -asCONTAINER ID IMAGE COMMAND ... SIZE9ebdba5a8445 gin-blog-docker-scratch "./go-gin-example" ... 0 B (virtual 132 MB)427ee79e6857 gin-blog-docker "./go-gin-example" ... 0 B (virtual 946 MB)从结果而言,占用大小以Scratch既为基础的容器完胜,完成目标
Mysql挂载数据卷
假如若不做任何干预,在每次启动一个Mysql容器时,数据库都是空的。另外容器删除之后,数据就丢失了(还有各类意外情况),非常糟糕!
数据卷
数据卷是被设计为保存持久化数据的,它的生命周期独立于容器,Docker不会在容器被删除后自动删除数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用docker rm -v这个命令
数据卷是一个可以感知一个或多个容器使用的特殊目录,它绕过UFS,可以提供很多有用的特性:
- 数据卷可以在容器之间共享和重用
- 对数据卷的修改会立马体现
- 对数据卷的更新,不会影响附加
- 数据卷默认会一直存在,甚至容器被删除
注意:数据卷的使用,而不是Linux下对目录或文件进行mount,替换中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的数据卷。
如何挂载
首先创建一个目录用于存放数据卷;示例目录/data/docker-mysql,注意--name原本名称为mysql的容器,需要将其删除docker rm
$ docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=rootroot -v /data/docker-mysql:/var/lib/mysql -d mysql54611dbcd62eca33fb320f3f624c7941f15697d998f40b24ee535a1acf93ae72创建成功,检查目录/data/docker-mysql,下面多了很多数据库文件
验证
接下来交由你进行验证,目标是创建一些测试表和数据,然后删除当前容器,重新创建的容器,数据库数据也依然存在(当然了数据卷指向要一致)
我已验证完毕,你呢?