赞
踩
Go 语言支持跨平台交叉编译,也就是说可以在 Windows 或 Mac 平台下编写代码,并且将代码编译成能够在 Linux amd64 服务器上运行的程序。
对于简单的项目,通常只需要将编译后的二进制文件拷贝到服务器上,然后设置为后台守护进程运行即可。
编译可以通过以下命令或编写 makefile 来操作。
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./bin/bluebell
如果嫌弃编译后的二进制文件太大,可以在编译的时候加上-ldflags "-s -w"参数去掉符号表和调试信息,一般能减小20%的大小。
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o ./bin/bluebell
编译好 bluebell 项目后,相关必要文件的目录结构如下:
├── bin │ └── bluebell ├── conf │ └── config.yaml ├── static │ ├── css │ │ └── app.0afe9dae.css │ ├── favicon.ico │ ├── img │ │ ├── avatar.7b0a9835.png │ │ ├── iconfont.cdbe38a0.svg │ │ ├── logo.da56125f.png │ │ └── search.8e85063d.png │ └── js │ ├── app.9f3efa6d.js │ ├── app.9f3efa6d.js.map │ ├── chunk-vendors.57f9e9d6.js │ └── chunk-vendors.57f9e9d6.js.map └── templates └── index.html
直接执行文件:
./bin/bluebell
nohup 用于在系统后台不挂断地运行命令,不挂断指的是退出执行命令的终端也不会影响程序的运行。
可以使用 nohup 命令来运行应用程序,使其作为后台守护进程运行。由于在主流的 Linux 发行版中都会默认安装 nohup 命令工具,我们可以直接输入以下命令来启动我们的项目:
sudo nohup ./bin/bluebell conf/config.yaml > nohup_bluebell.log 2>&1 &
其中:
> nohup_bluebell.log
表示将命令的标准输出重定向到 nohup_bluebell.log 文件2>&1
表示将标准错误输出也重定向到标准输出中,结合上一条就是把执行命令的输出都定向到 nohup_bluebell.log 文件上面的命令执行后会返回进程 id:[1] 6338
也可以通过以下命令查看 bluebell 相关活动进程:
ps -efl | grep bluebell
输出:
root 6338 4048 0 08:43 pts/0 00:00:00 ./bin/bluebell conf/config.yaml
root 6376 4048 0 08:43 pts/0 00:00:00 grep --color=auto bluebell
Supervisor 是业界流行的一个通用的进程管理程序,它能将一个普通的命令行进程变为后台守护进程,并监控该进程的运行状态,当该进程异常退出时能将其自动重启。
sudo yum install epel-release
sudo yum install supervisor
Supervisor 的配置文件为:/etc/supervisord.conf
,Supervisor 所管理的应用的配置文件放在 /etc/supervisord.d/
目录中,这个目录可以在 supervisord.conf
中的include
配置。
[include]
files = /etc/supervisord.d/*.conf
启动supervisor服务:
sudo supervisord -c /etc/supervisord.conf
在/etc/supervisord.d目录下创建一个名为bluebell.conf的配置文件,具体内容如下。
[program:bluebell] ;程序名称
user=root ;执行程序的用户
command=/data/app/bluebell/bin/bluebell /data/app/bluebell/conf/config.yaml ;执行的命令
directory=/data/app/bluebell/ ;命令执行的目录
stopsignal=TERM ;重启时发送的信号
autostart=true
autorestart=true ;是否自动重启
stdout_logfile=/var/log/bluebell-stdout.log ;标准输出日志位置
stderr_logfile=/var/log/bluebell-stderr.log ;标准错误日志位置
创建好配置文件之后,重启supervisor服务:
sudo supervisorctl update # 更新配置文件并重启相关的程序
查看bluebell的运行状态:
sudo supervisorctl status bluebell
supervisorctl status # 查看所有任务状态
supervisorctl shutdown # 关闭所有任务
supervisorctl start 程序名 # 启动任务
supervisorctl stop 程序名 # 关闭任务
supervisorctl reload # 重启supervisor
在需要静态文件分离、需要配置多个域名及证书、需要自建负载均衡层等稍复杂的场景下,我们一般需要搭配第三方的web服务器(Nginx、Apache)来部署我们的程序。
正向代理可以简单理解为客户端的代理,魔法属于正向代理。
反向代理可以简单理解为服务器的代理,通常说的 Nginx 和 Apache 就属于反向代理。
Nginx 是一个免费的、开源的、高性能的 HTTP 和反向代理服务,主要负责负载一些访问量比较大的站点。Nginx 可以作为一个独立的 Web 服务,也可以用来给 Apache 或是其他的 Web 服务做反向代理。相比于 Apache,Nginx 可以处理更多的并发连接,而且每个连接的内存占用的非常小。
EPEL 仓库中有 Nginx 的安装包。如果没有安装过 EPEL,可以通过运行下面的命令来完成安装:
sudo yum install epel-release
安装nginx
sudo yum install nginx
安装完成后,执行下面的命令设置Nginx开机启动:
sudo systemctl enable nginx
启动Nginx
sudo systemctl start nginx
查看Nginx运行状态:
sudo systemctl status nginx
通过上面的方法安装的 nginx,所有相关的配置文件都在 /etc/nginx/
目录中。Nginx 的主配置文件是 /etc/nginx/nginx.conf
。
默认还有一个nginx.conf.default
的配置文件示例,可以作为参考。可以为多个服务创建不同的配置文件(建议为每个服务(域名)创建一个单独的配置文件),每一个独立的 Nginx 服务配置文件都必须以 .conf结尾,并存储在 /etc/nginx/conf.d
目录中。
默认会将conf.d下的conf文件都包含进来
nginx -s stop # 停止 Nginx 服务
nginx -s reload # 重新加载配置文件
nginx -s quit # 平滑停止 Nginx 服务
nginx -t # 测试配置文件是否正确
worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; access_log /var/log/bluebell-access.log; error_log /var/log/bluebell-error.log; location / { proxy_pass http://127.0.0.1:8084; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } }
执行下面的命令检查配置文件语法:
nginx -t
执行下面的命令重新加载配置文件:
nginx -s reload
接下来就是打开浏览器查看网站是否正常了。
当然还可以使用 nginx 的 upstream 配置来添加多个服务器地址实现负载均衡。
worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; upstream backend { server 127.0.0.1:8084; # 这里需要填真实可用的地址,默认轮询 #server backend1.example.com; #server backend2.example.com; } server { listen 80; server_name localhost; access_log /var/log/bluebell-access.log; error_log /var/log/bluebell-error.log; location / { proxy_pass http://backend/; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } }
还可以有选择的将静态文件部分的请求直接使用 nginx 处理,而将 API 接口类的动态处理请求转发给后端的 Go 程序来处理。
worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; server_name bluebell; access_log /var/log/bluebell-access.log; error_log /var/log/bluebell-error.log; # 静态文件请求 location ~ .*\.(gif|jpg|jpeg|png|js|css|eot|ttf|woff|svg|otf)$ { access_log off; expires 1d; root /data/app/bluebell; } # index.html页面请求 # 因为是单页面应用这里使用 try_files 处理一下,避免刷新页面时出现404的问题 location / { root /data/app/bluebell/templates; index index.html; try_files $uri $uri/ /index.html; } # API请求 location /api { proxy_pass http://127.0.0.1:8084; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } }
前后端的代码没必要都部署到相同的服务器上,也可以分开部署到不同的服务器上,下图是前端服务将 API 请求转发至后端服务的方案。
上面的部署方案中,所有浏览器的请求都是直接访问前端服务,而如果是浏览器直接访问后端API服务的部署模式下,如下图。
此时前端和后端通常不在同一个域下,我们还需要在后端代码中添加跨域支持。
使用github.com/gin-contrib/cors
库来支持跨域请求。
最简单的允许跨域的配置是使用cors.Default(),它默认允许所有跨域请求。
func main() {
router := gin.Default()
// same as
// config := cors.DefaultConfig()
// config.AllowAllOrigins = true
// router.Use(cors.New(config))
router.Use(cors.Default())
router.Run()
}
此外,还可以使用cors.Config自定义具体的跨域请求相关配置项:
package main import ( "time" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() // CORS for https://foo.com and https://github.com origins, allowing: // - PUT and PATCH methods // - Origin header // - Credentials share // - Preflight requests cached for 12 hours router.Use(cors.New(cors.Config{ AllowOrigins: []string{"https://foo.com"}, AllowMethods: []string{"PUT", "PATCH"}, AllowHeaders: []string{"Origin"}, ExposeHeaders: []string{"Content-Length"}, AllowCredentials: true, AllowOriginFunc: func(origin string) bool { return origin == "https://github.com" }, MaxAge: 12 * time.Hour, })) router.Run() }
使用docker的主要目标是容器化,也就是为应用程序提供一致的环境,而不依赖于它运行的主机。
package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/", hello) server := &http.Server{ Addr: ":8888", } fmt.Println("server startup...") if err := server.ListenAndServe(); err != nil { fmt.Printf("server startup failed, err:%v\n", err) } } func hello(w http.ResponseWriter, _ *http.Request) { w.Write([]byte("hello liwenzhou.com!")) }
镜像(image)包含运行应用程序所需的所有东西——代码或二进制文件、运行时、依赖项以及所需的任何其他文件系统对象。
要创建Docker镜像(image)必须在配置文件中指定步骤。这个文件默认通常称之为Dockerfile。
具体内容如下:
FROM golang:alpine # 为我们的镜像设置必要的环境变量 ENV GO111MODULE=on \ CGO_ENABLED=0 \ GOOS=linux \ GOARCH=amd64 # 移动到工作目录:/build WORKDIR /build # 将代码复制到容器中 COPY . . # 将我们的代码编译成二进制可执行文件app RUN go build -o app . # 移动到用于存放生成的二进制文件的 /dist 目录 WORKDIR /dist # 将二进制文件从 /build 目录复制到这里 RUN cp /build/app . # 声明服务端口 EXPOSE 8888 # 启动容器时运行的命令 CMD ["/dist/app"]
Dockerfile解析:
CMD ["/dist/app"]
。在项目目录下,执行下面的命令创建镜像,并指定镜像名称为goweb_app:
docker build . -t goweb_app
等待构建过程结束,输出如下提示:
Successfully tagged goweb_app:latest
执行下面的命令来运行镜像:
docker run -p 8888:8888 goweb_app
标志位-p用来定义端口绑定。由于容器中的应用程序在端口8888上运行,如果要绑定到另一个端口,则可以使用-p $HOST_PORT:8888。例如-p 5000:8888。
Go程序编译之后会得到一个可执行的二进制文件,其实在最终的镜像中是不需要go编译器的。
Docker的最佳实践之一是通过仅保留二进制文件来减小镜像大小,为此,将使用一种称为多阶段构建的技术,这意味着将通过多个步骤构建镜像。
FROM golang:alpine AS builder # 为我们的镜像设置必要的环境变量 ENV GO111MODULE=on \ CGO_ENABLED=0 \ GOOS=linux \ GOARCH=amd64 # 移动到工作目录:/build WORKDIR /build # 将代码复制到容器中 COPY . . # 将我们的代码编译成二进制可执行文件 app RUN go build -o app . ################### # 接下来创建一个小镜像 ################### FROM scratch # 从builder镜像中把/dist/app 拷贝到当前目录 COPY --from=builder /build/app / # 需要运行的命令 ENTRYPOINT ["/app"]
使用这种技术,剥离了使用golang:alpine
作为编译镜像来编译得到二进制可执行文件的过程,并基于scratch生成一个简单的、非常小的新镜像。
然后将二进制文件从命名为builder的第一个镜像中复制到新创建的scratch镜像中。
具体目录结构如下:
bubble ├── README.md ├── bubble ├── conf │ └── config.ini ├── controller │ └── controller.go ├── dao │ └── mysql.go ├── example.png ├── go.mod ├── go.sum ├── main.go ├── models │ └── todo.go ├── routers │ └── routers.go ├── setting │ └── setting.go ├── static │ ├── css │ │ ├── app.8eeeaf31.css │ │ └── chunk-vendors.57db8905.css │ ├── fonts │ │ ├── element-icons.535877f5.woff │ │ └── element-icons.732389de.ttf │ └── js │ ├── app.007f9690.js │ └── chunk-vendors.ddcb6f91.js └── templates ├── favicon.ico └── index.html
需要将templates、static、conf三个文件夹中的内容拷贝到最终的镜像文件中。更新后的Dockerfile如下:
FROM golang:alpine AS builder # 为我们的镜像设置必要的环境变量 ENV GO111MODULE=on \ CGO_ENABLED=0 \ GOOS=linux \ GOARCH=amd64 # 移动到工作目录:/build WORKDIR /build # 复制项目中的 go.mod 和 go.sum文件并下载依赖信息 COPY go.mod . COPY go.sum . RUN go mod download # 将代码复制到容器中 COPY . . # 将我们的代码编译成二进制可执行文件 bubble RUN go build -o bubble . ################### # 接下来创建一个小镜像 ################### FROM scratch COPY ./templates /templates COPY ./static /static COPY ./conf /conf # 从builder镜像中把/dist/app 拷贝到当前目录 COPY --from=builder /build/bubble / # 需要运行的命令 ENTRYPOINT ["/bubble", "conf/config.ini"]
这里把COPY静态文件的步骤放在上层,把COPY二进制可执行文件放在下层,争取多使用缓存。
因为项目中使用了MySQL,所以可以选择使用如下命令启动一个MySQL容器,它的别名为mysql8019;root用户的密码为root1234;
挂载容器中的/var/lib/mysql到本地的/Users/q1mi/docker/mysql目录;内部服务端口为3306,映射到外部的13306端口。
docker run --name mysql8019 -p 13306:3306 -e MYSQL_ROOT_PASSWORD=root1234 -v /Users/q1mi/docker/mysql:/var/lib/mysql -d mysql:8.0.19
这里需要修改一下程序中配置的MySQL的host地址为容器别名,使它们在内部通过别名(此处为mysql8019)联通。
[mysql]
user = root
password = root1234
host = mysql8019
port = 3306
db = bubble
修改后重新构建bubble_app镜像:
docker build . -t bubble_app
这里运行bubble_app容器的时候需要使用–link的方式与上面的mysql8019容器关联起来,具体命令如下:
docker run --link=mysql8019:mysql8019 -p 8888:8888 bubble_app
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。