微服务框架 Kratos 使用Docker部署微服务

423 阅读9分钟

微服务框架 Kratos 之部署微服务

Kratos在部署微服务的时候需要一个额外的参数:

  • -conf: 指向配置文件的目录

所以在部署kratos服务时, 需要注意, 添加参数, 另外kratos的main入门在cmd/<server>/main.go,在build时使用go build ./...不像普通的go build, 这也是一个注意的地方. 然后就是kratos它支持GRPC, 你的服务可能会起HTTP与GRPC的端口

Kratos 与 Web库 Gin 的区别

KratosGin 是两个流行的 Go 语言微服务框架,但它们的设计目标和适用场景有所不同。

  • Kratos

    • 全栈框架:Kratos 是一个全栈的微服务框架,提供了从服务发现、负载均衡、API 网关、认证授权、限流熔断、日志监控等全方位的支持。
    • 模块化设计:Kratos 采用了模块化设计,开发者可以根据需要选择和组合不同的模块,灵活构建微服务。
    • 协议支持:Kratos 支持多种协议,包括 HTTP、gRPC、Dubbo 等,适用于复杂的微服务架构。
    • 社区支持:Kratos 由字节跳动开源,拥有活跃的社区和丰富的文档资源。
  • Gin

    • 轻量级框架:Gin 是一个轻量级的 Web 框架,专注于快速开发高性能的 HTTP 服务。
    • 简单易用:Gin 提供了简洁的 API 和中间件机制,适合快速开发简单的 RESTful API。
    • 性能优越:Gin 在性能方面表现出色,适合高并发场景。
    • 社区支持:Gin 拥有庞大的用户群体和丰富的第三方插件生态。

部署方式

常见的部署有三种:

  1. 二进制
  2. Docker
  3. Kubernetes

这里不讲二进制部署, 着重讲解Docker 和 Kubernetes

Docker 和 Kubernetes

Docker

  • 容器化技术:Docker 是一种轻量级的虚拟化技术,允许开发者将应用程序及其依赖打包成容器,确保应用程序在不同环境中的一致性。
  • 隔离性和可移植性:Docker 容器提供了良好的隔离性和可移植性,可以在任何支持 Docker 的平台上运行。
  • 开发和测试:Docker 适用于开发和测试环境,可以快速构建和运行应用程序。

Kubernetes

  • 容器编排平台:Kubernetes 是一个开源的容器编排平台,用于自动化容器化应用程序的部署、扩展和管理。
  • 高可用性和扩展性:Kubernetes 提供了高可用性和自动伸缩能力,确保应用程序的稳定运行。
  • 服务发现和负载均衡:Kubernetes 内置了服务发现和负载均衡机制,简化了微服务之间的通信。
  • 滚动更新和回滚:Kubernetes 支持滚动更新和回滚,确保应用程序的平滑升级。

Docker 和 Kubernetes 部署的区别

Docker 部署

  • 单节点部署:Docker 部署通常在一个节点上运行容器,适用于小型应用或开发测试环境。
  • 手动管理:需要手动管理容器的启动、停止和监控。
  • 简单配置:使用 Docker Compose 文件可以轻松管理多个容器的配置。

Kubernetes 部署

  • 多节点集群:Kubernetes 部署在一个多节点集群上运行容器,适用于生产环境。
  • 自动管理:Kubernetes 自动管理容器的生命周期,包括自动重启、负载均衡和故障恢复。
  • 复杂配置:使用 Kubernetes 配置文件(如 YAML)管理集群和服务,配置较为复杂。

如何选型想必读者已然心里有数, 下一篇文章讲解Kubernetes

Dockerfile 实践

我们将从一个可能的普通的Dockerfile 开始,逐步优化到最终的最佳实践 Dockerfile。

普通的 Dockerfile

网上搜索了一个go的Dockerfile, 内容如下:

dockerfile
# 打包依赖阶段使用 golang 作为基础镜像
FROM golang:1.14 as builder

# 启用 go module
ENV GO111MODULE=on \
    GOPROXY=https://goproxy.cn,direct

WORKDIR /app

COPY . .

# 指定 OS 等,并 go build
RUN GOOS=linux GOARCH=amd64 go build .

# 由于我不止依赖二进制文件,还依赖 views 文件夹下的 html 文件还有 assets 文件夹下的一些静态文件
# 所以我将这些文件放到了 publish 文件夹
RUN mkdir publish && cp toc-generator publish && \
    cp -r views publish && cp -r assets publish

# 运行阶段指定 scratch 作为基础镜像
FROM alpine

WORKDIR /app

# 将上一个阶段 publish 文件夹下的所有文件复制进来
COPY --from=builder /app/publish .

# 指定运行时环境变量
ENV GIN_MODE=release \
    PORT=80

EXPOSE 80

ENTRYPOINT ["./toc-generator"]

这个Dockerfile乍一看没什么问题, 但是你仔细分析, 它的缺点有以下几点:

  1. 依赖下载未分离

    • 在构建阶段,所有的文件(包括源代码和依赖文件)都被复制到容器中,然后再进行构建。这样会导致每次构建时都需要重新下载依赖文件,增加了构建时间。
  2. 缓存利用率低

    • 由于依赖下载和源代码复制在一个步骤中完成,Docker 缓存机制无法充分利用。每次源代码发生变化时,都会重新下载依赖文件。
  3. 缺少详细的构建信息

    • 构建命令 go build . 没有提供详细的构建信息,如版本号、构建时间等,这在调试和版本控制中可能会带来不便。
  4. 安全性和权限管理

    • 使用 scratch 作为基础镜像虽然体积小,但缺乏必要的系统工具和安全特性。使用 alpine 作为基础镜像时,没有创建非特权用户来运行应用程序,存在安全隐患。
  5. 环境变量设置不完整

    • 虽然设置了 GIN_MODE 和 PORT,但可能还需要设置其他环境变量,如数据库连接字符串、Redis 地址等。
  6. 日志和监控支持不足

    • 没有配置日志和监控相关的工具和环境变量,这对于生产环境来说是一个重要的缺失。

优化后的Dockerfile

# syntax=docker/dockerfile:1
# https://docs.docker.com/go/dockerfile-reference/

# 定义基础镜像的 Golang 版本
ARG GOIMAGE=golang:1.23.3-alpine3.20

FROM --platform=$BUILDPLATFORM ${GOIMAGE} AS build
WORKDIR /src

# 版本号
ARG VERSION=latest

# Go 的环境变量, 例如 alpine 镜像不内置 gcc, 则关闭 CGO 很有效
ARG GOOS=linux
ARG GOARCH=amd64
ARG CGOENABLED=0

# Go 的环境变量, 例如 alpine 镜像不内置 gcc, 则关闭 CGO 很有效
ARG GO_PROXY=https://proxy.golang.com.cn,direct

COPY . .

# 设置环境变量
RUN go env -w GOPROXY=$GO_PROXY

# 利用 Docker 层缓存机制,单独下载依赖项,提高后续构建速度。
# 使用缓存挂载和绑定挂载技术,避免不必要的文件复制到容器中。
RUN --mount=type=cache,target=/go/pkg/mod/ \
    --mount=type=bind,source=go.sum,target=go.sum \
    --mount=type=bind,source=go.mod,target=go.mod \
    go mod download -x

# 获取代码版本号,用于编译时标记二进制文件
RUN --mount=type=cache,target=/go/pkg/mod/ \
    --mount=type=bind,target=. \
    GOOS=$GOOS \
    GOARCH=$GOARCH \
    CGOENABLED=$CGOENABLED \
    go build -o /bin ./...

COPY ./configs /bin/configs

FROM alpine:latest AS final
# 从构建阶段复制编译好的 Go 应用程序到运行阶段
COPY --from=build /bin/user /bin/
COPY --from=build /bin/configs /bin/configs

# 用户进程 ID
ARG UID=10001

# 后端程序的 HTTP/gRPC 端口
ARG HTTP_PORT=30001
ARG GRPC_PORT=30002

# 修改镜像源
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories

# 安装应用运行必需的系统证书和时区数据包
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone

# 指定容器对外暴露的端口号
EXPOSE $HTTP_PORT $GRPC_PORT

VOLUME /data/conf

# 设置容器启动时执行的命令
CMD ["/bin/user", "-conf", "/data/conf"]

它有如下优化:

  1. 多阶段构建

    • 构建阶段:使用 Golang 镜像构建应用程序,生成二进制文件。
    • 运行阶段:使用 Alpine 镜像作为最终运行环境,减少镜像大小。
  2. 环境变量

    • 使用 ARG 和 ENV 指令传递和设置构建和运行时的环境变量,确保灵活性和可配置性。
  3. 缓存和绑定挂载

    • 使用 --mount 指令利用 Docker 层缓存机制,避免不必要的文件复制,提高构建速度。
  4. 安全性和性能

    • 关闭 CGO (CGOENABLED=0),减少依赖,提高安全性和性能。
    • 创建非特权用户运行应用程序,增强容器安全性。
  5. 多架构支持

    • 使用 docker buildx 构建多架构的二进制文件,确保在不同平台上的兼容性。

Docker构建

单平台构建

构建Docker所属的当前平台与架构的二进制文件

VERSION=dev
REPOSITORY="team/backend"
Docker 容器在 Linux 内核上运行,即便是在 macOS 或 Windows 环境中。
如果使用docker构建时传递 GOOS=darwin 会导致构建的二进制文件不兼容于 Linux 环境,从而出现 exec format error
所以在使用docker构建时的目标平台的GOOS应该为 linux,而非 darwin。
docker build . \
  --progress=plain \
  -t dev/app:dev \
  --build-arg CGOENABLED=0 \
  --build-arg GOIMAGE=golang:1.23.3-alpine3.20 \
  --build-arg GOOS=linux \
  --build-arg GOARCH=arm64 \
  --build-arg VERSION=$VERSION \
  --build-arg HTTP_PORT=30001 \
  --build-arg GRPC_PORT=30002

多架构支持

构建多架构的二进制文件, 如果是Docker Desktop, 需要在Docker Desktop 启用 containerd 映像存储

VERSION=v1.0.0
REPOSITORY="tiktok/orders"
GOOS=linux
GOARCH=amd64
HTTP_PORT=30001
GRPC_PORT=30002
PLATFORM_1=linux/amd64
docker buildx build . \
  --progress=plain \
  -t $REPOSITORY:$VERSION \
  --build-arg CGOENABLED=0 \
  --build-arg GOIMAGE=golang:1.23.3-alpine3.20 \
  --build-arg VERSION=$VERSION \
  --build-arg HTTP_PORT=$PORT \
  --build-arg GRPC_PORT=$PORT \
  --build-arg GOOS=$GOOS \
  --build-arg GOARCH=$GOARCH \
  --platform $PLATFORM_1 \
  --load

推送

register="myrepo.com"
docker tag $REPOSITORY:$VERSION $register/$REPOSITORY:$VERSION
docker push $register/$REPOSITORY:$VERSION

拉取

GOOS=linux
GOARCH=amd64
docker pull $register/$REPOSITORY:$VERSION --platform $GOOS/GOARCH

运行

docker run \
--rm \
-p 30001:30001 \
-p 30002:30002 \
-e GIN_MODE=release \
-e DB_SOURCE="postgresql://postgres:postgres@localhost:5432/simple_bank?sslmode=disable" \
$REPOSITORY:$VERSION

使用Docker compose 来优化 Docker 命令行

使用命令行来构建和运行并不是一个流行的推荐, 它有几个缺点:

1. 可维护性和一致性

  • 手动操作容易出错:手动输入命令容易出错,尤其是在复杂的构建和部署过程中。
  • 缺乏版本控制:命令行操作通常不会记录在版本控制系统中,难以追踪和回溯。
  • 配置不一致:不同开发人员或团队成员可能使用不同的命令和参数,导致配置不一致。

2. 自动化和集成

  • 缺乏自动化:手动构建和运行镜像不适合持续集成/持续交付(CI/CD)流程,难以实现自动化。
  • 依赖管理:手动管理依赖关系和构建顺序复杂且容易出错。

3. 可扩展性和管理

  • 难以管理多服务:对于多个微服务,手动管理每个服务的构建和运行非常繁琐。
  • 资源管理和调度:手动操作难以实现资源管理和调度,特别是在多节点集群环境中。

4. 环境一致性

  • 环境差异:手动操作可能导致开发、测试和生产环境之间的差异,影响应用程序的稳定性和可靠性。
  • 依赖环境:手动构建和运行依赖于特定的开发环境,难以在不同环境中复现。

5. 安全性

  • 权限管理:手动操作难以确保权限管理的一致性和安全性。
  • 审计和日志:手动操作缺乏详细的审计和日志记录,难以追踪和排查问题。

利用Docker compose 来优化 Dockerfile, 让我们把一个docker run改造一下:

docker run \
--rm \
-p 30001:30001 \
-p 30002:30002 \
-e GIN_MODE=release \
-e DB_SOURCE="postgresql://postgres:postgres@localhost:5432/simple_bank?sslmode=disable" \
$REPOSITORY:$VERSION

变成:

services:

  users:
    image: tiktok/users:prod
    platform: linux/amd64
    ports:
      - "30001:30001"
      - "30002:30002"
    container_name: users
    restart: always
    networks:
      - app-network
    environment:
      - DB_SOURCE=postgresql://postgres:postgres@postgres17:5432/simple_bank?sslmode=disable
      - REDIS_ADDRESS=redis7:6379
    command:
      - "/bin/user"
      - "-conf"
      - "/bin/configs"

逐行解释:

  1. 服务定义

    
    services:
    
  2. 服务名称

    users:
    
    • 定义一个名为 users 的服务。
  3. 镜像

    image: tiktok/users:prod
    
    • 指定要使用的 Docker 镜像。这里使用的是 tiktok/users:prod 镜像,标签为 prod,表示生产环境的镜像。
  4. 平台

    platform: linux/amd64
    
    • 指定目标平台。这里指定为 linux/amd64,表示该镜像是为 x86_64 架构的 Linux 系统构建的。
  5. 端口映射

    
    ports:
      - "30001:30001"
      - "30002:30002"
    
    • 将宿主机的端口 30001 和 30002 分别映射到容器内的端口 30001 和 30002。这使得容器内的服务可以通过宿主机的这些端口访问。
  6. 容器名称

    container_name: users
    
    • 指定容器的名称为 users。这有助于在 Docker 命令中更方便地引用该容器。
  7. 重启策略

    restart: always
    
    • 设置容器的重启策略为 always,表示无论容器因何原因退出,Docker 都会自动重启该容器。
  8. 环境变量

    yaml
    深色版本
    environment:
      - DB_SOURCE=postgresql://postgres:postgres@postgres17:5432/simple_bank?sslmode=disable
      - REDIS_ADDRESS=redis7:6379
    
    • 设置容器的环境变量。这里设置了两个环境变量:

      • DB_SOURCE:指定数据库连接字符串,用于连接 PostgreSQL 数据库。
      • REDIS_ADDRESS:指定 Redis 服务器的地址。
  9. 命令

    yaml
    深色版本
    command:
      - "/bin/user"
      - "-conf"
      - "/bin/configs"
    
    • 指定容器启动时执行的命令。这里启动 /bin/user 可执行文件,并传入 -conf 和 /bin/configs 参数

最后, 启动应用:

docker compose up