首页 >后端开发 >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