Docker 完成作坊式部署私活级别 Golang 程序 0x00:基础服务发布
前言
大家好,我是一笑,在这篇文章中将为大家介绍如何利用 Docker 部署私活级 Golang 程序,最终我们要通过最低的成本来实现 goalng 的自动打包,部署操作。
首先我们来定义一下什么是私活级,私活大家都懂,就是我们利用业余去赚一些外快,一般情况下后端基本上都是一个人独立完成的,有的时候甚至真的只有一个人,没有运维,没有测试,一个人要负责整个软件的生命周期,从开发迭代到后面的运维部署,所以在本文中私活级
将指代是没有企业级运维能力的研发场景。
在这种情况,我们可以利用 Docker 和 脚本命令来完成一些简单的自动化操作,来减少我们的时间成本以及运维成本。
由于刚开始是一个人进行研发工作,所以我们并不会涉及到 gitlab, Jenkins 这些 DevOPS 工具,毕竟这些会增加我们的研发成本,如果非要部署这些服务,我的建议是可以使用树莓派,同时将 gitlab 更换为 gitea ,因为 gitea 可以更加节省服务器资源,同时一定要定时备份,这点很重要,以免树莓派损坏导致的数据丢失。
在后面我会不断追加这一系列的文章,其中也会讲到 DevOPS , 不过这些不是这篇文章的主要内容,在这里不做过多说明,接下来让我们正式开始私活级 Golang 程序的部署工作。
环境准备
这里为了节省成本,就不从云服务厂商购买服务器了,使用虚拟机来进行搭建。
- Golang 1.18
- Goland IDE
- VitralBox
- Windows(我本机使用的是 Windows 11)
虚拟机操作系统 | 网络模式 | 虚拟机 IP | 虚拟机 配置 |
---|---|---|---|
CentOS 7 | 桥接网卡 | 192.168.1.100 | cup: 2 mem:2G |
基础 Go 服务发布
代码准备
首先,我们来初始化我们的项目代码,因为这里的项目并不重要,所以我们用 gin 框架简单输出一个 Hello World 来模拟我们的项目代码,项目结构如下:
PS C:\Users\maksim\WorkSpace> tree .\proejct\
文件夹 PATH 列表
卷序列号为 5402-60A2
│ go.sum
│ main.go
main.go:
package main
import (
"github.com/gin-gonic/gin"
"log"
)
func main() {
r := gin.Default()
r.Handle("GET", "/", func(context *gin.Context) {
context.String(200, "hello world!")
})
err := r.Run(":8080")
if err != nil {
log.Fatalln(err)
}
}
代码很简单,就是利用 gin 返回 hello world!,我们将以此为基础来进行部署,首先我们不使用 docker 进行部署,而是通过 ssh 将 编译好的代码同步到服务器上。
由于我的开发机是 Windows, 所以这个时候需要进行交叉编译,在Windows 平台上编译 Linux 可执行文件,我们先来编写一个 build.bat 脚本,用来辅助我们自动化生成可执行文件,其脚本路径与 mian.go 同级。
set GOOS=linux
set GOARCH=amd64
go build -o build/server main.go
我们来执行一下脚本文件。
# 执行 build 脚本生成可执行文件
PS C:\Users\maksim\WorkSpace\proejct> .\build.bat
C:\Users\maksim\WorkSpace\proejct>set GOOS=linux
C:\Users\maksim\WorkSpace\proejct>set GOARCH=amd64
C:\Users\maksim\WorkSpace\proejct>go build -o build/server main.go
# 查看 build 目录下文件
PS C:\maksim\guozh\WorkSpace\proejct> ls .\build\
目录: C:\Users\maksim\WorkSpace\proejct\build
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2022-10-02 14:00 10062347 server
部署 Linux
我的 Linux 安装选用了最小化安装,简单配置了IP,没有对 Linux 进行用户设置,所以用 root 用户进行登录,所以为了安全第一件事就是为 Linux 设置新的账户。
# 1. ssh 登录
ssh [email protected]
# 2. 输入密码,这里略过
# 3. 新建账户
# 3.1 新增用户
useradd -m maksim
# 3.2 给用设置密码
passwd maksim
# 3.3 给 maksim root 权限
cat >> /etc/sudoers <<EOF #添加用户可以知晓所有命令(可以根据需要修改),切换不用输入密码
user1 ALL=(ALL) NOPASSWD: ALL
EOF
# 4. 给新用户 SSH 登录权限
# 4.1 编辑 sshd_config 文件
vi /etc/ssh/sshd_config
# 4.2 找到 AllowUsers, 解除注释,并且加入用户名,用户名之间用空格隔开
AllowUsers username username2
# 4.3 重启 ssdh 服务
service sshd restart
这样主要是为了 Linux 的系统安全,在线上拥有 root 权限其实是一个非常可怕的情况,毕竟如果你想的话 rm -rf /*
也是完全没有问题的,所以一般公司都不会给开发人员 root 权限,线上一般也就是给只读权限的账户。
现在我们已经拥有了新的账户,接下来我们来部署应用,现在使用新的账户进行操作。
# 在根目录下新建data 目录,其中包括 soft,data,server
sudo mkdir -p /data/{soft,data,server}
[maksim@localhost data]$ cd /data/ && ls -l
total 0
drwxr-xr-x. 2 root root 6 Oct 2 14:29 data
drwxr-xr-x. 2 root root 6 Oct 2 14:29 server
drwxr-xr-x. 2 root root 6 Oct 2 14:29 soft
- soft 目录是用来存放我们安装的应用,例如 redis, MySQL, nginx
- data 用来存放应用数据,例如Nginx 日志,应用日志等。
- server 用来存放我们的应用服务,也就是我们要部署的内容;
我们在 soft 目录下新建 api-server 目录,该目录用来存放我们的 helloworld 程序。
cd server && sudo mkdir api-server
创建完目录后,我们来设置免密登录,这是因为我们需要上传我们的应用程序,如果是使用 scp 上传每一次都需要传入密码,就很麻烦。
首先我们打开 cmd 生成本机的 ssh 密匙。
PS C:\Users\maksim> ssh-keygen -t rsa -C "[email protected]"
Generating public/private rsa key pair.
Enter file in which to save the key (C:\Users\maksim/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in C:\Users\maksim/.ssh/id_rsa
Your public key has been saved in C:\Users\maksim/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:pwc27Wi3bpeGM8vZJ6MoM2Hoz5MWMU1SZdMl3cfVYLY [email protected]
The key's randomart image is:
+---[RSA 3072]----+
| ...+..o=++|
| . .. ..+..=|
| + E .|
| o .. |
| . oS o |
| . +. B |
| . . ++ +. . |
| ..B. ==+* . |
| oo=.oBB.+ |
+----[SHA256]-----+
ssh-keygen -t rsa -C "[email protected]"
这个命令的具体意义,大家可自行百度,这里不做赘述,因为这篇文章的重点不在这里。
PS C:\Users\maksim\.ssh> type .\id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC8gUVLCs1Vbei3L6lYWrr7QTWee/qEnC9t2oNeiSKsQMtRTRUh+oCIqSR6zL+5giK1bthRUJnKaJZqrJPSsdaiispaoduoiIsklfdjJLIOJI@saslE= [email protected]
我们可以到查看该目录下的 ssh ,在这里的ssh 请不要复制,因为是我瞎写的。
我们将生成好的 id_rsa.pub 文件内容添加到 Linux 中。
PS C:\Users\maksim\.ssh> type ./id_rsa.pub | ssh [email protected] "mkdir .ssh && cat >> .ssh/authorized_keys"
注意:这里的脚本中有一段是 mkdir .ssh 这是因为我的虚拟机中没有该目录,如果报错了的话,可以将 makdir .ssh && 删掉。
在这里我们使用了 root 账户,这是因为可以避免很多权限问题。
我们来测试一下是否可以免密码登录:
PS C:\Users\maksim\.ssh> ssh [email protected]
Last login: Sun Oct 2 16:18:41 2022
[root@localhost ~]#
现在我们来编写 deploy.sh 脚本,用来部署我们的应用程序:
scp ./build/server root:/data/server/api-server/server
我们通过 scp 将编译好的目录上传上去,我们执行完成后就会发现,server 文件就被上传到了我们的Linux当中:
[root@localhost api-server]# ls -l
total 9828
-rw-r--r--. 1 root root 10062347 Oct 2 16:39 server
但是这个时候,细心的你发现这个文件没有可执行权限啊,很简单,我们来继续晚上 deploy.bat 脚本,我们使用 ssh 命令 来给该文件配置权限。
set server_path=/data/server/api-server/server
scp ./build/server [email protected]:%server_path%
set chmod_exec_cmd= chmod +x %server_path%
ssh [email protected] %chmod_exec_cmd%
这是我们修改后的脚本,使用 bat 变量来配置要上传的路径,并且在最后执行了权限修改命令,我们来执行一下,再看看 Linux 中 server 的变化。
[root@localhost api-server]# ls -l
total 9828
-rwxr-xr-x. 1 root root 10062347 Oct 2 16:45 server
我们可以看到,这样一来 server 就具备了可执行权限,我们可以执行一下 server。
[maksim@localhost api-server]$ ./server
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[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 / --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8080
我们先让它在后台运行
#我们现在用的是 maksim 账户,但是创建目录的时候用的 root ,所以我们需要将权限转交给 maksim 避免出现权限错误
[maksim@localhost api-server] chown -R /data/data maksim
[maksim@localhost api-server] nohup ./server &>> /data/data/server.log 2>&1 &
运行后我们执行 curl 命令来判断程序是否运行成功:
[maksim@localhost api-server]$ curl localhost:8080
hello world!
在这里还有一点需要进行优化,那就是我们的服务如果在运行阶段是无法重新部署的,我们可以试验一下:
[maksim@localhost api-server]$ ls -l
total 9828
-rwxr-xr-x. 1 root root 10062347 Oct 2 17:10 server
现在最新的上传时间是17:10,我们来执行上传脚本,然后在观察时间的变化。
PS C:\Users\maksim\WorkSpace\proejct> .\delopy.bat
C:\Users\maksim\WorkSpace\proejct>set server_path=/data/server/api-server/server
C:\Users\maksim\WorkSpace\proejct>scp ./build/server [email protected]:/data/server/api-server/server
scp: /data/server/api-server/server: Text file busy
C:\Users\maksim\WorkSpace\proejct>set chmod_exec_cmd= chmod +x /data/server/api-server/server
C:\Users\maksim\WorkSpace\proejct>ssh [email protected] chmod +x /data/server/api-server/server
我们可以看到,这个时候的脚本执行是没有报错的,但是我们在 Linux 中执行 ls -l
。
[maksim@localhost api-server]$ ls -l
total 9828
-rwxr-xr-x. 1 root root 10062347 Oct 2 17:10 server
所以我们需要将 Linux 中的进程杀死,然后在重新启动脚本。
:: 设置服务文件路径
set server_path=/data/server/api-server/server
:: 将进程杀死
ssh [email protected] "ps -ef | grep server | awk '{print $2}' | xargs kill -9"
:: 发送应用程序文件
scp ./build/server [email protected]:%server_path%
:: 设置执行权限
set chmod_exec_cmd= chmod +x %server_path%
ssh [email protected] %chmod_exec_cmd%
ssh [email protected] "nohup %server_path% &>> /data/data/server.log 2>&1 &"
我们执行完成后再去 Linux 上看一下。
[maksim@localhost api-server]$ ps -ef | grep server
root 2210 1 0 17:19 ? 00:00:00 /data/server/api-server/server
maksim 2216 1867 0 17:19 pts/1 00:00:00 grep --color=auto server
[maksim@localhost api-server]$ curl localhost:8080
hello world!
好了,到目前为止,我们的开发,打包,部署就已经基本完成了,下面我们开始对项目进行 docker 化,如果你不想打包 docker 那么到这里你还有以下几点需要去做:
- 使用 supervisor 监控应用程序
- 如果真的只有一台节点,那么最好做应用的优雅重启,避免在部署阶段导致服务中断,而与此同时,还需要改造 deploy.sh 以支持优雅重启。
小结
在这一篇文章讲解了,如何利用 bat 脚本、ssh 命令来部署我们的 golang 应用程序。
其实在工作中,我真的有遇到过这种发布模式,很简单,几行脚本就搞定了大套的发布流程,看起来很作坊,很不成体系,但是确实成本最低的实现方案。
在下一章中我们将开始利用 Docker 来部署我们的项目。