Docker 完成作坊式部署私活级别 Golang 程序 0x01:利用 Docker 构建应用
我们在上一小节实现了应用的自动部署,但是其中有一个小问题,我们的部署依赖于线下打包,然后使用 scp 发给服务器,这个时候如果应用程序体积过大或者网络情况不好,那么就会非常耗时,你不能保证你时时刻刻都在一个非常良好的网络条件下。
这个时候我们就需要使用构建服务器了,但是使用 Jenkins 也需要新的服务器资源才可以,而且还需要进行学习,学习也是一个成本,最近呢,你还比较忙,真是抽不出来时间,那怎么办呢?
其实 Jenkins 的自动部署也是要依赖于我们的脚本的,我们使用 shell 完全可以解决这个问题,毕竟在没有自动化 CICD 之前,大家都是用 shell 解决的。
注意:这里是私活级!!!!
注意:这里是私活级!!!!
注意:这里是私活级!!!!
重要的事情说三遍,在一般的公司不会允许这么搞的!
想要在远程构建,首先我们需要解决代码如何同步到服务器上。
将代码上传至服务器
如果想要在线上进行打包,那么首先就需要将代码上传至服务器。
Goland 是一个强大的 IDE,其中为我们提供了上传代码的功能,这样一来我们就可以达到上传代码到服务器的目的。
在正常的公司里是肯定不允许这样操作的!
首先点击 Tools -> Deployment -> Configuration
点击 + 号 -> sftp -> 输入 sever name
开始填写 SFTP 内容,首先我们要新建 ssh 配置。
因为我们之前已经添加了免密登录,所以我们在添加完IP信息后,选择 key pair ,他会默认帮我们选择好 id_rsa 文件,然后点击 Test Connection 看看能否链接服务器。
回到 Deployment 配置页面我们点击 Root path配置目录,我们输入 /data/data。
接下来点击 Mappings 来配置我们要将代码上传到哪个文件夹下,点击 Deployment path 并点击鼠标右键新建一个 src 的目录用于存放我们的源代码。
然后再点击 Excluded Paths 来过滤掉不想上传的文件。
build,.idea 两个目录其实是不用上传的,点击完成,接下来我们选中main.go 和 go.mod 这两个文件将其上传至 pro。
接下来我们到 Linux 服务器上看一下代码是否已经被上传到服务器上。
[root@localhost src]# cd /data/data/src/ && ll
total 8
-rw-r--r--. 1 root root 1049 Oct 2 13:43 go.mod
-rw-r--r--. 1 root root 253 Oct 2 16:49 main.go
利用 Docker 构建应用程序
我们其实也可以在服务器上安装 golang 然后直接利用 go build 命令来构建应用程序,但是这其实涉及到污染生产环境的问题,所以我们采用 docker 的方式来进行打包,首先我们先安装 docker。
# 更新 yum
sudo yum update
# 安装依赖
sudo yum install -y yum-utils device-mapper-persistent-data lvm2 -y
# 设置 docker 源
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# 安装 docker
sudo yum install docker-ce -y
# 启动 docker
sudo systemctl start docker
# 随开机启动
sudo systemctl enable docker
# 查看 docker 版本
docker version
因为我们的本地 Golang 用的是1.18.6,所以我们在 docker hub 上选择了 1.18.6-apline3.16 作为我们的打包工具。
选择 alpine 版本还有一个原因就是它的体积是非常小的,既然是“私活”,那就应该从各个方面来思考如何节省资源。
[root@localhost src]# docker pull golang:1.18.6-alpine3.16
如果你觉着 pull 的速度太慢,那可以换成国内的源,这里我就不赘述了。
下载完成后,我们来执行 docker run 命令来打包试试:
[root@localhost src]# docker run --rm -it \
-v /data/data:/data/data \
-w /data/data/src/ \
e CGO_ENABLED=0 \
-e GOPROXY=https://goproxy.cn \
golang:1.18.6-alpine3.16 go build -o ../server-build-docker main.go
go: downloading github.com/gin-gonic/gin v1.8.1
go: downloading github.com/gin-contrib/sse v0.1.0
go: downloading github.com/mattn/go-isatty v0.0.14
go: downloading golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
go: downloading github.com/go-playground/validator/v10 v10.10.0
go: downloading github.com/pelletier/go-toml/v2 v2.0.1
go: downloading github.com/ugorji/go/codec v1.2.7
go: downloading google.golang.org/protobuf v1.28.0
go: downloading gopkg.in/yaml.v2 v2.4.0
go: downloading golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069
go: downloading github.com/go-playground/universal-translator v0.18.0
go: downloading github.com/leodido/go-urn v1.2.1
go: downloading golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
go: downloading golang.org/x/text v0.3.6
go: downloading github.com/go-playground/locales v0.14.0
--rm 在docker 容器执行完成后会直接被删掉,我们首先是将/data/data 目录挂在到容器中,之后将工作目录切换到 /data/data/src 目录下进行打包操作。
[root@localhost data]# cd /data/data && ls -l
total 9880
-rwxr-xr-x. 1 root root 10110954 Oct 2 23:42 server-build-docker
-rw-rw-r--. 1 maksim maksim 2783 Oct 2 17:20 server.log
drwxr-xr-x. 2 root root 49 Oct 2 23:41 src
我们可以看到文件被生成了,并且被设置成了可执行文件。
这里存在一个问题,每一次都会去重新下载依赖,这样会影响打包的速度,我们可以将 gopath 目录找到然后将其目录挂载出来。
# 创建目录用来保存 go build 的依赖文件
mkdir /data/data/godep
# 执行新的 go build 命令
docker run --rm -it -v /data/data:/data/data \
-v /data/data/godep:/go \
-w /data/data/src/ \
e CGO_ENABLED=0 \
-e GOPROXY=https://goproxy.cn \
golang:1.18.6-alpine3.16 go build -o ../server-build-docker main.go
# 第二次执行
docker run --rm -it -v /data/data:/data/data \
-v /data/data/godep:/go \
-w /data/data/src/ \
e CGO_ENABLED=0 \
-e GOPROXY=https://goproxy.cn \
golang:1.18.6-alpine3.16 go build -o ../server-build-docker main.go
在我们第二次执行的时候就不会再去下载依赖了。
改进编译、部署脚本
这个时候我们再去完善我们的部署脚本,我们新建一个 remote-build-deploy 脚本,完成构建以及部署操作。
:: 构建应用
ssh [email protected] "bash /data/data/src/remote-build.sh"
这一次,我们的脚本变成了一个行,所有的编译环节的脚本我们都放到了服务器上,这是因为 ssh 单条执行命令的效率实在是太低了,并且部署脚本单独抽出来,我们可以在其中做一些逻辑判断,如下:
#!/bin/bash
build_path=/data/data/server-build-docker
build() {
echo "开始执行编译"
docker run --rm -i \
-v /data/data:/data/data \
-v /data/data/godep:/go \
-w /data/data/src/ \
-e GOPROXY=https://goproxy.cn \
-e CGO_ENABLED=0 \
golang:1.18.6-alpine3.16 go build -o $build_path main.go
if [ -f $build_path ]; then
echo "build 成功:目录 "$build_path
else
echo "build 失败,没有找到该文件"
exit 20
fi
}
build
当完成后,我们去判断是否有文件产出,也就是我们的常说的"制品",如果没有就赶紧报错!
之后就是部署脚本,与构建脚本一样,部署脚本我们也放到了远程执行。
:: 部署应用
ssh [email protected] "bash /data/data/src/remote-deploy.sh"
#!/bin/bash
server_path=/data/server/api-server/server
build_path=/data/data/server-build-docker
deploy() {
if [ ! -f $build_path ]; then
echo "build 失败,没有找到该文件"
exit 20
fi
process=$(ps -ef | grep $server_path | grep -v "grep" | wc -l)
if [[ process == 0 ]]; then
echo "当前没有执行,可以直接启动,启动中...."
# 进程不存在直接移动目录4
mv $build_path $server_path
nohup $server_path &>>/data/data/server.log 2>&1 &
else
echo "当前有执行,杀死进程"
ps -ef | grep server | awk '{print $2}' | xargs kill -9
echo "杀死成功,执行运动命令"
mv $build_path $server_path
echo "开始执行"
nohup $server_path &>>/data/data/server.log 2>&1 &
fi
echo "执行成功";
}
test() {
sleep 2
curl localhost:8080
echo ""
}
deploy
test
- 首先校验的是是否有制品存在
- 如果有制品,判断当前是否有正在执行的进程
- 如果有在执行的进程,那么就干掉,然后移动、执行。
- 如果没有则直接移动、执行
小结
到目前为止,我们完成了自动化远程 docker 编译,在下一小节,我们来实现 api-server 的 docker 化。