部署Go語言項目
本文以部署 Go Web 程式為例,介紹了在 CentOS7 伺服器上部署 Go 語言程式的若幹方法。
獨立部署
Go 語言支援跨平台交叉編譯,也就是說我們可以在 Windows 或 Mac 平台下編寫代碼,并且将代碼編譯成能夠在 Linux amd64 伺服器上運作的程式。
對于簡單的項目,通常我們隻需要将編譯後的二進制檔案拷貝到伺服器上,然後設定為背景守護程序運作即可。
編譯
編譯可以通過以下指令或編寫 makefile 來操作。
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./bin/bluebell
下面假設我們将本地編譯好的 bluebell 二進制檔案、配置檔案和靜态檔案等上傳到伺服器的/data/app/bluebell目錄下。
補充一點,如果嫌棄編譯後的二進制檔案太大,可以在編譯的時候加上-ldflags "-s -w"參數去掉符号表和調試資訊,一般能減小20%的大小。
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags “-s -w” -o ./bin/bluebell
如果還是嫌大的話可以繼續使用 upx 工具對二進制可執行檔案進行壓縮。
我們編譯好 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
nohup
nohup 用于在系統背景不挂斷地運作指令,不挂斷指的是退出執行指令的終端也不會影響程式的運作。
我們可以使用 nohup 指令來運作應用程式,使其作為背景守護程序運作。由于在主流的 Linux 發行版中都會預設安裝 nohup 指令工具,我們可以直接輸入以下指令來啟動我們的項目:
sudo nohup ./bin/bluebell conf/config.yaml > nohup_bluebell.log 2>&1 &
其中:
./bluebell conf/config.yaml是我們應用程式的啟動指令
nohup ... &表示在背景不挂斷的執行上述應用程式的啟動指令
> nohup_bluebell.log表示将指令的标準輸出重定向到 nohup_bluebell.log 檔案
2>&1表示将标準錯誤輸出也重定向到标準輸出中,結合上一條就是把執行指令的輸出都定向到 nohup_bluebell.log 檔案
上面的指令執行後會傳回程序 id
[1] 6338
當然我們也可以通過以下指令檢視 bluebell 相關活動程序:
ps -ef | 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
此時就可以打開浏覽器輸入http://伺服器公網ip:端口檢視應用程式的展示效果了。

supervisor
Supervisor 是業界流行的一個通用的程序管理程式,它能将一個普通的指令行程序變為背景守護程序,并監控該程序的運作狀态,當該程序異常退出時能将其自動重新開機。
首先使用 yum 來安裝 supervisor:
如果你還沒有安裝過 EPEL,可以通過運作下面的指令來完成安裝,如果已安裝則跳過此步驟:
sudo yum install epel-release
安裝 supervisor
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
輸出:
bluebell RUNNING pid 10918, uptime 0:05:46
最後補充一下常用的supervisr管理指令:
supervisorctl status # 檢視所有任務狀态
supervisorctl shutdown # 關閉所有任務
supervisorctl start 程式名 # 啟動任務
supervisorctl stop 程式名 # 關閉任務
supervisorctl reload # 重新開機supervisor
接下來就是打開浏覽器檢視網站是否正常了。
搭配nginx部署
在需要靜态檔案分離、需要配置多個域名及證書、需要自建負載均衡層等稍複雜的場景下,我們一般需要搭配第三方的web伺服器(Nginx、Apache)來部署我們的程式。
正向代理與反向代理
正向代理可以簡單了解為用戶端的代理,你通路牆外的網站用的那個屬于正向代理。
反向代理可以簡單了解為伺服器的代理,通常說的 Nginx 和 Apache 就屬于反向代理。
Nginx 是一個免費的、開源的、高性能的 HTTP 和反向代理服務,主要負責負載一些通路量比較大的站點。Nginx 可以作為一個獨立的 Web 服務,也可以用來給 Apache 或是其他的 Web 服務做反向代理。相比于 Apache,Nginx 可以處理更多的并發連接配接,而且每個連接配接的記憶體占用的非常小。
使用yum安裝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配置檔案
通過上面的方法安裝的 nginx,所有相關的配置檔案都在 /etc/nginx/ 目錄中。Nginx 的主配置檔案是 /etc/nginx/nginx.conf。
預設還有一個nginx.conf.default的配置檔案示例,可以作為參考。你可以為多個服務建立不同的配置檔案(建議為每個服務(域名)建立一個單獨的配置檔案),每一個獨立的 Nginx 服務配置檔案都必須以 .conf結尾,并存儲在 /etc/nginx/conf.d 目錄中。
Nginx常用指令
補充幾個 Nginx 常用指令。
nginx -s stop # 停止 Nginx 服務
nginx -s reload # 重新加載配置檔案
nginx -s quit # 平滑停止 Nginx 服務
nginx -t # 測試配置檔案是否正确
Nginx反向代理部署
我們推薦使用 nginx 作為反向代理來部署我們的程式,按下面的内容修改 nginx 的配置檔案。
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分離靜态檔案請求
上面的配置是簡單的使用 nginx 作為反向代理處理所有的請求并轉發給我們的 Go 程式處理,其實我們還可以有選擇的将靜态檔案部分的請求直接使用 nginx 處理,而将 API 接口類的動态處理請求轉發給後端的 Go 程式來處理。
下面繼續修改我們的 nginx 的配置檔案來實作上述功能。
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 請求轉發至後端服務的方案。
前後端分開部署方案1
上面的部署方案中,所有浏覽器的請求都是直接通路前端服務,而如果是浏覽器直接通路後端API服務的部署模式下,如下圖。
此時前端和後端通常不在同一個域下,我們還需要在後端代碼中添加跨域支援。
前後端分開部署方案2
這裡使用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()
}
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()
}