首頁 >後端開發 >Golang >如何使用 Docker 部署 Go 應用程式

如何使用 Docker 部署 Go 應用程式

Linda Hamilton
Linda Hamilton原創
2024-11-04 10:19:30708瀏覽

Docker 是一個容器化平台,可以簡化應用程式的打包、分發和部署。您可以利用 Go 和 Docker 的優勢來提高應用程式的效率、可移植性和安全性。

本教學致力於教您如何使用 Docker 建置和部署 Go 應用程式。您將透過使用 Gorilla Mux 和 GORM 套件建立 RESTful API 來學習,並將其容器化和部署。

步驟 1:設定您的開發環境

您需要在電腦上安裝 Go 和 Docker,才能使用 Docker 建置和容器化您的 Go 應用程式。

確保您的系統上安裝了 Go 和 Docker。您可以從 Go 官方下載網站下載 Go,從 Docker Hub 下載 Docker。如果尚未造訪該網頁,請造訪該網頁,然後按照您的特定作業系統的安裝說明進行操作。

本文介紹如何使用 Docker 部署 Go 應用程序,並介紹有關安裝和設定 Docker 和 Postgres 資料庫的更多信息,包括容器化 Go 應用程式。

安裝完成後,根據需要設定環境變數和路徑來設定您的Go開發環境。確保您有一個具有所需目錄結構的工作 Go 工作區。

此外,您還可以熟悉 Docker 的命令列介面 (CLI) 和基本 Docker 概念。

為此專案建立一個新目錄,並執行 go mod init 指令將該目錄初始化為 Go 專案。

go mod init

初始化 Go 專案後,執行此命令將 GORM 和 Gorilla Mux 套件作為依賴項新增至您的專案。

go get github.com/gorilla/mux

go get gorm.io/gorm
go get gorm.io/driver/postgres

您將使用 Gorilla Mux 套件進行路由。 GORM 套件提供了一個接口,供您使用 Go 類型進行 SQL 資料庫操作以及您安裝的驅動程式套件(在本例中為 Postgres)。

第 2 步:建立 Go 應用程式

在本教程中,您將使用流行的 Go 分層架構風格並使用介面與我們應用程式的各個元件進行互動。

這是應用程式的目錄結構。

go mod init
  1. Dockerfile:Dockerfile 是建置 Docker 映像的設定檔。您將根據 API 編寫此文件的內容。
  2. cmd:cmd 目錄通常包含應用程式的入口點。 server 子目錄表示 API 伺服器是專案的主要元件。 cmd/server 中的 main.go 檔案包含應用程式的入口點。
  3. internal:內部目錄有助於組織應用程式的內部套件。內部包不應導出,這使它們成為隱藏實現細節的絕佳場所。
    • http:此子目錄將包含 HTTP 相關程式碼,包括路由處理程序和可能的 API 中介軟體。
      • handlers.go:您將在此文件中包含 HTTP 請求處理程序。
      • users.go:您將在此指定與使用者管理相關的 HTTP 處理程序。
    • models:目錄將包含與資料庫相關的程式碼和資料模型。
    • database.go:此檔案將包含用於初始化和連接資料庫的程式碼。
    • migrations.go:此檔案處理資料庫架構遷移,確保您的資料庫架構符合您的應用程式的要求。
    • users.go:此文件將包含與使用者與資料庫互動相關的資料模型或結構定義。
    • users:目錄將包含使用者特定的邏輯。
      • user.go:此檔案包含與資料庫實作互動的使用者管理相關的函數和結構。 HTTP 實作與此處的函數進行互動。

這個專案結構看起來組織良好,清楚地分離了不同組件之間的關注點。隨著 Go API 的發展,這個組織可以讓您更輕鬆地維護和擴展它。

這不是 Go 標準。然而,許多 Go 開發人員和開源專案在您的應用程式中使用這種結構。

步驟 2a:編寫資料庫實現

您將為您的應用程式設定資料庫功能。您必須使用結構定義模型,連接到資料庫,並為資料庫上的插入操作設定遷移。

這是資料庫實作所需的導入清單。

go get github.com/gorilla/mux

go get gorm.io/gorm
go get gorm.io/driver/postgres

第一個任務是定義一個與您的應用程式的資料庫架構相符的支柱。 GORM 提供了用於指定欄位的附加選項和約束的標籤。

.
├── Dockerfile
├── cmd
│   └── server
│       └── main.go
└── internal
    ├── http
    │   ├── handlers.go
    │   └── users.go
    ├── models
    │   ├── database.go
    │   ├── migrations.go
    │   └── users.go
    └── users
        └── user.go

6 directories, 11 files

User 結構體表示處理資料庫中使用者資料的模型。

在您的database.go 檔案中,宣告一個結構體來封裝資料庫連線實例。您將使用該結構從資料庫實現套件的其他部分連接到資料庫。

go mod init

接下來,建立一個資料庫連接函數,將資料庫實作與資料庫程式連接到資料庫:

go get github.com/gorilla/mux

go get gorm.io/gorm
go get gorm.io/driver/postgres

NewDatabase 函數建立一個新的資料庫實例並建立與資料庫的連線。它會傳回一個指向資料庫實例的指針,並且在此過程中發生錯誤(如果有)。

成功連接資料庫後,您可以使用以下函數為資料庫實作設定遷移功能:

.
├── Dockerfile
├── cmd
│   └── server
│       └── main.go
└── internal
    ├── http
    │   ├── handlers.go
    │   └── users.go
    ├── models
    │   ├── database.go
    │   ├── migrations.go
    │   └── users.go
    └── users
        └── user.go

6 directories, 11 files

MgrateDB 函數使用資料庫用戶端 AutoMigrate 函數為 User 結構設定自動遷移,如果在此過程中遇到任何問題,則傳回錯誤。

步驟 2b:定義資料庫實現的函數

在為資料庫架構定義結構的 users.go 檔案中,您可以繼續定義資料庫實作的函數。

這裡是負責資料庫CRUD操作的CreateUser、GetUserByID、UpdateUser和DeleteUser函數。

package models

import (
    // imports from the user implementation
    "BetterApp/internal/users"

    "context"
    "gorm.io/gorm"
    "fmt"
    "gorm.io/driver/postgres"
    "gorm.io/gorm/schema"
    "os"
)

您的使用者實作將呼叫這些函數來存取資料庫功能。

步驟 2c:編寫使用者實現

您的使用者實作在將資料從資料庫中繼到 HTTP 實作方面發揮著重要作用。

您將定義一個與資料庫實作中的結構相符的結構,並將 JSON 標籤新增至欄位以供使用;然後,您將定義使用 HTTP 實作中的資料呼叫資料庫函數的函數。

以下是使用者實作所需的導入:

// internal/models/users.go

type User struct {
    gorm.Model
    Username string `gorm:"unique;not null"`
    Email    string `gorm:"unique;not null"`
    IsActive bool   `gorm:"not null"`
}

這是一個有 JSON 標籤的結構。 gorm.Model 欄位中的 json:"-" 指定您要從 JSON 操作中排除該欄位。

// internal/models/database.go

type Database struct {
    Client *gorm.DB
}

接下來,您將聲明一個接口,其中包含用戶實現函數的方法、用戶實現的服務結構以及初始化服務實現的函數。

// internal/models/database.go

func NewDatabase() (*Database, error) {

    // Construct a connection string using environment variables for database configuration.
    configurations := fmt.Sprintf("host=%v port=%v user=%v password=%v dbname=%v sslmode=%v",
        os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_USERNAME"),
        os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME"), os.Getenv("SSL_MODE"))

    // Open a connection to the database using GORM and PostgreSQL driver.
    db, err := gorm.Open(postgres.New(postgres.Config{
        DSN:                  configurations,
        PreferSimpleProtocol: true,
    }), &gorm.Config{NamingStrategy: schema.NamingStrategy{
        SingularTable: true,
    }})
    if err != nil {
        return nil, err
    }

    // Enable connection pooling by configuring maximum idle and open connections.
    sqlDB, err := db.DB()
    if err != nil {
        return nil, err
    }
    sqlDB.SetMaxIdleConns(10)
    sqlDB.SetMaxOpenConns(100)

    // Return the Database instance with the established database connection.
    return &Database{
        Client: db,
    }, nil
}

介面和服務將協助管理使用者實作以外的與使用者相關的操作。

接下來,您可以定義呼叫資料庫實作的 UserService 結構實作的方法。

// internal/models/migrations.go

func (d *Database) MigrateDB() error {
    log.Println("Database Migration in Process...")

    // Use GORM AutoMigrate to migrate all the database schemas.
    err := d.Client.AutoMigrate(&User{})
    if err != nil {
        return err
    }

    log.Println("Database Migration Complete!")
    return nil
}

CreateUser、GetUserByID、UpdateUser 和 DeleteUser 函數負責呼叫資料庫實作上的 CRUD 操作。 HTTP 實作將會呼叫這些函數來存取資料庫。

步驟 2c:編寫 HTTP 實現

HTTP 實作是應用程式的一部分,用於接收傳入請求並與之互動。

這是您在 HTTP 實作中所需的匯入清單:

go mod init

首先,宣告一個結構體並包含一個 Router 實例、一個 HTTP 實例和一個使用者服務實例。

go get github.com/gorilla/mux

go get gorm.io/gorm
go get gorm.io/driver/postgres

然後建立一個傳回指向 Handler 結構的指標的函數,您可以在其中設定伺服器和處理程序。

.
├── Dockerfile
├── cmd
│   └── server
│       └── main.go
└── internal
    ├── http
    │   ├── handlers.go
    │   └── users.go
    ├── models
    │   ├── database.go
    │   ├── migrations.go
    │   └── users.go
    └── users
        └── user.go

6 directories, 11 files

NewHandler 函數設定並設定 HTTP 請求處理程序,使其準備好處理特定服務的傳入 HTTP 請求,同時定義伺服器設定和路由。

您在 NewHandler 函數中呼叫的 mapRoutes 函數透過將路由對應到各自的處理函數來設定路由。

package models

import (
    // imports from the user implementation
    "BetterApp/internal/users"

    "context"
    "gorm.io/gorm"
    "fmt"
    "gorm.io/driver/postgres"
    "gorm.io/gorm/schema"
    "os"
)

接下來,定義處理函數及其功能。 這裡有CreateUser、GetUserByID、UpdateUser和DeleteUser函數,它們負責攔截HTTP請求並根據操作回應。

// internal/models/users.go

type User struct {
    gorm.Model
    Username string `gorm:"unique;not null"`
    Email    string `gorm:"unique;not null"`
    IsActive bool   `gorm:"not null"`
}

現在,您可以編寫啟動伺服器的功能了。

// internal/models/database.go

type Database struct {
    Client *gorm.DB
}

Serve 函數在指定連接埠上啟動伺服器,如果過程中發生錯誤,則傳回錯誤。

步驟 2d:耦合實作並運行應用程式

匯入 main.go 檔案中的實作以耦合實作並執行您的應用程式。

// internal/models/database.go

func NewDatabase() (*Database, error) {

    // Construct a connection string using environment variables for database configuration.
    configurations := fmt.Sprintf("host=%v port=%v user=%v password=%v dbname=%v sslmode=%v",
        os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_USERNAME"),
        os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME"), os.Getenv("SSL_MODE"))

    // Open a connection to the database using GORM and PostgreSQL driver.
    db, err := gorm.Open(postgres.New(postgres.Config{
        DSN:                  configurations,
        PreferSimpleProtocol: true,
    }), &gorm.Config{NamingStrategy: schema.NamingStrategy{
        SingularTable: true,
    }})
    if err != nil {
        return nil, err
    }

    // Enable connection pooling by configuring maximum idle and open connections.
    sqlDB, err := db.DB()
    if err != nil {
        return nil, err
    }
    sqlDB.SetMaxIdleConns(10)
    sqlDB.SetMaxOpenConns(100)

    // Return the Database instance with the established database connection.
    return &Database{
        Client: db,
    }, nil
}

您可以在 main.go 檔案中宣告一個 Run 函數來實例化應用程式的啟動,然後在 main 函數中呼叫該函數。

// internal/models/migrations.go

func (d *Database) MigrateDB() error {
    log.Println("Database Migration in Process...")

    // Use GORM AutoMigrate to migrate all the database schemas.
    err := d.Client.AutoMigrate(&User{})
    if err != nil {
        return err
    }

    log.Println("Database Migration Complete!")
    return nil
}

Run 函數建立一個資料庫實例,初始化遷移功能,初始化 HTTP 和 User 實作並啟動伺服器。

您可以在主函數中呼叫 Run 函數來啟動您的應用程式。

// internal/models/users.go

func (d *Database) CreateUser(ctx context.Context, user *users.User) error {
    newUser := &User{
        Username: user.Username,
        Email:    user.Email,
        IsActive: false,
    }

    if err := d.Client.WithContext(ctx).Create(newUser).Error; err != nil {
        return err
    }

    return nil
}

// GetUserByID returns the user with a specified id
func (d *Database) GetUserByID(ctx context.Context, id int64) (users.User, error) {
    user := users.User{}
    if err := d.Client.WithContext(ctx).Where("id = ?", id).First(&user).Error; err != nil {
        return users.User(User{}), err
    }
    return users.User(User{
        Username: user.Username,
        Email:    user.Email,
        IsActive: user.IsActive,
    }), nil
}

// UpdateUser updates an existing user in the database
func (d *Database) UpdateUser(ctx context.Context, updatedUser users.User, id uint) error {
    // Check if the user with the specified ID exists
    var existingUser User
    if err := d.Client.WithContext(ctx).Where("id = ?", id).First(&existingUser).Error; err != nil {
        return err
    }

    // Update the fields of the existing user with the new values
    existingUser.Username = updatedUser.Username
    existingUser.Email = updatedUser.Email
    existingUser.IsActive = updatedUser.IsActive

    // Save the updated user back to the database
    if err := d.Client.WithContext(ctx).Save(&existingUser).Error; err != nil {
        return err
    }

    return nil
}

// DeleteUser deletes a user from the database by their ID

func (d *Database) DeleteUser(ctx context.Context, id uint) error {
    // Check if the user with the specified ID exists
    var existingUser User
    if err := d.Client.WithContext(ctx).Where("id = ?", id).First(&existingUser).Error; err != nil {
        return err
    }

    // Delete the user from the database
    if err := d.Client.WithContext(ctx).Delete(&existingUser).Error; err != nil {
        return err
    }

    return nil
}

在考慮使用 Docker 對其進行容器化之前,應用程式應該運作良好。

Step3:編寫 Dockerfile

現在您已經成功建置並運行了程序,您可以繼續使用 Docker 將其容器化。

您的 Dockerfile 將有兩個階段:建置階段和最終階段。這種方法可以減少影像大小,透過減少攻擊面來最大限度地降低安全風險,確保高效的運行時效能,並促進不同開發和部署階段的可重複性。

您還將使用 Alpine Linux 作為 Docker 映像的基礎映像,因為它們更有效率、更安全,並且採用極簡設計,可實現更小的映像大小、更快的建置速度和更少的攻擊面。

步驟 3a:建置階段

使用 Dockerfile 中的建置和最終階段可以有效地建立 Docker 映像。建置階段從包含建置工具和相依性的基礎映像開始,編譯應用程式工件,並產生可能較大的中間映像。

這是建置階段的 Dockerfile 的內容:

go mod init
  1. FROM golang:1.20-alpine AS build:此行指定建置階段的基礎鏡像。它以標有版本 1.20 的官方 Golang Docker 映像開始,基於 Alpine Linux。 AS build 部分為該階段提供了一個名稱“build”,您可以稍後引用。
  2. WORKDIR /app:此行將容器內的工作目錄設定為 /app。 Docker 將在該目錄中執行後續命令。
  3. 複製。 .:此指令將目前目錄的內容(可能是您的 Go 應用程式原始碼和其他必要檔案)複製到容器內的 /app 目錄中。
  4. RUN go build -o server ./cmd/server:這是建立 Go 應用程式的命令。它使用 go build 命令編譯目前目錄中的 Go 程式碼,並將二進位檔案輸出為 server./cmd/server 參數是應用程式程式碼相對於 /app 目錄的位置。

步驟 3b:最後階段

最後階段採用較小的基礎映像,僅複製必要的執行時間元件,並產生針對生產最佳化的緊湊映像。

以下是最後階段的 Dockerfile 內容:

go mod init
  1. FROM alpine:latest:在最後階段,您可以從 Alpine Linux 基礎鏡像開始,latest 標籤指定 Alpine Linux 的最新可用版本。
  2. WORKDIR /app:此行將容器內的工作目錄設定為 /app。 Docker 將在該目錄中執行後續命令。
  3. COPY --from=build /app/server .:此命令將名為server 的二進位檔案從上一個「建置階段」複製到/app目錄中在最終的容器內。這個二進位檔案是您在建置階段建置的已編譯的 Go 應用程式。
  4. EXPOSE 8080:在這裡,您指定您的應用程式將偵聽連接埠 8080。這是一個聲明,實際上並沒有打開該連接埠;這是記錄您的應用程式期望使用哪個連接埠的方法。
  5. CMD ["./server"]:當您執行基於鏡像的容器時,將執行此命令。它指定運行 server 二進位文件,這是您的 Go 應用程式。此命令在容器內啟動您的應用程式。

第 4 步:建置並執行 Docker 映像

編寫 Dockerfile 後,您可以繼續建置並執行該檔案。
執行此命令以使用 build 命令從檔案建立 Docker 映像。

go get github.com/gorilla/mux

go get gorm.io/gorm
go get gorm.io/driver/postgres

-t 標誌指定 Docker 映像的標籤為 betterapp,後面的點 (.) 指定您要在目前目錄中建置 Dockerfile。

您可以使用 run 指令執行映像,並使用 -p 標誌指定從容器到主機的連接埠對映。

.
├── Dockerfile
├── cmd
│   └── server
│       └── main.go
└── internal
    ├── http
    │   ├── handlers.go
    │   └── users.go
    ├── models
    │   ├── database.go
    │   ├── migrations.go
    │   └── users.go
    └── users
        └── user.go

6 directories, 11 files

隨後的 -e 標誌用於為您的應用程式指定環境變數。

第 5 步:使用 Docker 部署 Go 應用程式

Docker Compose 是一個容器編排工具,可以簡化多個 Docker 容器的使用。您可以使用 Docker compose 來編排您的 Go 應用程式及其元件。

您將使用 YAML 檔案來指定指令,Docker compose 將設定您的應用程式以節省您的時間和複雜性。

首先,使用以下命令建立一個 Docker Compose YAML 文件,然後在編輯器中開啟該文件:

package models

import (
    // imports from the user implementation
    "BetterApp/internal/users"

    "context"
    "gorm.io/gorm"
    "fmt"
    "gorm.io/driver/postgres"
    "gorm.io/gorm/schema"
    "os"
)

建立 Dockerfile 後,您可以開始編寫用於部署應用程式的命令和指令:

// internal/models/users.go

type User struct {
    gorm.Model
    Username string `gorm:"unique;not null"`
    Email    string `gorm:"unique;not null"`
    IsActive bool   `gorm:"not null"`
}

YAML 檔案定義了兩個服務:my-postgres(資料庫容器實例)和 Web 服務(在設定環境變數、連接埠和相依性之前您的 Go 應用程式)。

現在,您可以繼續使用 docker-compose build 指令來建置映像。

// internal/models/database.go

type Database struct {
    Client *gorm.DB
}

您的輸出應與此類似:

How to Deploy Go Applications With Docker

最後,您可以使用 docker-compose up 指令來執行容器。

go mod init

-d 標誌以分離模式運行容器,這使得它與終端會話無關。

這是運行指令的結果:

How to Deploy Go Applications With Docker

您可以關閉終端,容器應該繼續運作。

容器啟動後,您可以執行 CURL 請求來測試您的 API:

go get github.com/gorilla/mux

go get gorm.io/gorm
go get gorm.io/driver/postgres

恭喜,您已經使用 Docker 和 Docker Compose 成功部署並運行了一個工作 Go 應用程式。

結論

您已經學習如何使用 Docker 和 Docker Compose 建置和簡化 Go 應用程式的部署。當您繼續您的開發之旅時,您在這裡獲得的技能和理解將被證明是確保順利部署和卓越營運的重要資產。

考慮探索進階 Docker 功能,例如最佳化 Dockerfile 建置或為大型應用程式實作 Docker Swarm。

以上是如何使用 Docker 部署 Go 應用程式的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn