部署Go語言程式的N種方式

Q1mi發表於2020-09-20

部署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
│   └── 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 ./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:埠檢視應用程式的展示效果了。

bluebell效果bluebell效果

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/static;
        }

        # index.html頁面請求
        # 因為是單頁面應用這裡使用 try_files 處理一下,避免重新整理頁面時出現404的問題
        location / {
            root /data/app/bluebell/templates;
            index index.html;
            try_files $uri $uri/ /index.html;
        }

    # API請求
        location /api/v1 {
            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前後端分開部署方案1

上面的部署方案中,所有瀏覽器的請求都是直接訪問前端服務,而如果是瀏覽器直接訪問後端API服務的部署模式下,如下圖。

此時前端和後端通常不在同一個域下,我們還需要在後端程式碼中新增跨域支援。

前後端分開部署方案2前後端分開部署方案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()
}

此外,還可以使用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和Docker Compose部署Go Web應用,這裡就不再贅述了。

關於 bluebell

上文提及的 bluebell 專案是我個人錄製的 Go Web開發進階專案實戰課程中的專案,目前該課程已經錄製完成,共計80課時。想要了解該課程的同學歡迎猛戳以下連結:

或者可以關注我的個人微信公眾號:李文周,回覆bluebell獲取相關課程連結。


本文首發於我的個人部落格:liwenzhou.com,點選連結檢視完整Go語言教程。

相關文章