云运行服务的日志记录、跟踪与本地部署实践
1. 跟踪上下文与分布式跟踪
在处理请求并调用另一个云运行(Cloud Run)服务时,我们希望将第一个请求和第二个请求的日志分组显示。特别是在应用由多个微服务协同工作时,这一功能尤为重要。若下游服务出现问题,我们能知晓调用该服务的上游服务中发生了什么。
分布式跟踪是一个成熟且复杂的主题,这里仅作简要介绍。若要全面了解,可参考相关资料。
1.1 转发跟踪 ID
捕获传入的跟踪头并将跟踪 ID 转发到服务发起的请求中,云日志记录(Cloud Logging)会将所有请求添加到同一跟踪中。Google 前端(GFE)会为传入请求添加
X-Cloud-Trace-Context
头,并保留已有该头的请求的跟踪 ID。
在 Go 中实现跟踪 ID 转发的关键步骤如下:
1. 使用
crzerolog
包写入日志,将跟踪 ID 添加到日志中。
2. 以
idtoken
包能理解的方式(使用 Go 的请求上下文)将跟踪 ID 添加到所有传入请求中。
3. 将传入请求的上下文传递到传出请求,并使用
idtoken
HTTP 客户端发起请求。
idtoken
包也可用于向公共云运行服务发送请求,ID 令牌仅用于标识请求。
1.2 为所有传入请求准备跟踪 ID
idtoken
包需要请求上下文中的跟踪 ID 以与开源分布式跟踪工具 OpenCensus 兼容。可以使用 OpenCensus 包装 HTTP 处理程序:
// "go.opencensus.io/plugin/ochttp"
// "contrib.go.opencensus.io/exporter/stackdriver/propagation"
httpHandler := &ochttp.Handler{
Propagation: &propagation.HTTPFormat{},
Handler: handler,
}
http.ListenAndServe(":8080", httpHandler)
1.3 将请求上下文传递到传出请求
使用
idtoken
包发起请求时,应将传入请求的上下文传递到传出请求:
URL := "https://[SERVICE].run.app"
client, _ := idtoken.NewClient(context.Background(), URL)
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
req, _ := http.NewRequest("GET", URL, nil)
req = req.WithContext(r.Context()) // Pass context
client.Do(req)
})
以下是一个完整的端到端示例:
package main
import (
"context"
"os"
"contrib.go.opencensus.io/exporter/stackdriver/propagation"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/yfuruyama/crzerolog"
"go.opencensus.io/plugin/ochttp"
"google.golang.org/api/idtoken"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
client, _ := idtoken.NewClient(context.Background(), r.Host)
log.Ctx(r.Context()).Info().Msg("1st")
req, _ := http.NewRequest("GET", "https://"+r.Host+"/call", nil)
req = req.WithContext(r.Context())
client.Do(req)
})
mux.HandleFunc("/call",
func(w http.ResponseWriter, r *http.Request) {
log.Ctx(r.Context()).Info().Msg("2nd")
})
rootLogger := zerolog.New(os.Stdout)
middleware := crzerolog.InjectLogger(&rootLogger)
handler := middleware(mux)
httpHandler := &ochttp.Handler{
Propagation: &propagation.HTTPFormat{},
Handler: handler,
}
if err := http.ListenAndServe(":8080", httpHandler); err != nil {
log.Fatal().Msg("Can’t start service")
}
}
1.4 在云日志记录中查看跟踪上下文
将上述示例部署为云运行服务后,它会向自身发起请求并转发跟踪 ID。在云日志记录中查看跟踪的方法是:选取任意日志,点击带有蓝色条的药丸,然后选择“显示此跟踪的所有日志”。
2. 分布式跟踪的其他资源
这里对分布式跟踪的解释较为实用,实际上还有更多功能,如创建自定义跨度、控制采样率以及提高整个堆栈的可见性等。可以参考相关文档,使用 OpenCensus 库在 Go 应用中配置云跟踪(Cloud Trace)。
3. 基于云监控的日志指标
云监控(Cloud Monitoring)可监控云运行系统指标,如容器 CPU 和内存(利用率和分配)、请求延迟和计数等。结构化日志为云日志记录中的日志带来了大量有用的额外数据,云监控可以摄取这些数据,从而创建图表并设置基于日志的指标警报。
创建基于日志的指标的步骤如下:
1. 进入云日志记录中的“基于日志的指标”选项卡。
2. 点击“创建指标”按钮。
4. 结构化日志的总结
日志记录是构建生产就绪应用程序的重要部分。在云运行中使用结构化日志可获得以下好处:
| 类型 | 说明 |
| ---- | ---- |
| 日志级别 | 表示日志事件的重要性(调试、信息、警告、错误、致命、恐慌),是最常见的元数据类型。 |
| 业务元数据 | 将日志与订单或产品等逻辑概念关联,有助于理解日志并发现模式,但要注意避免添加个人信息。 |
| 源代码位置 | 知道打印日志的具体代码行,无需在代码库中搜索日志来源。 |
| 请求上下文 | 发生错误时,可读取处理该请求时写入的所有日志。 |
| 跟踪上下文 | 在调用下游云运行服务时传播请求上下文,可一起查看所有请求的日志。 |
云运行与云日志记录集成,无需强制使用特定于供应商的库和工具,可使用开源库改善日志记录体验。
5. 云运行与 Knative 服务
云运行与开源项目 Knative 服务的 API 兼容,这意味着可以轻松地将服务从云运行迁移到 Knative 服务。
5.1 可移植性的重要性
虽然不建议将本地的 Knative 服务作为开发环境(Docker Compose 是本地容器编排的轻量级方法),但了解可移植性很重要。在供应商单方面改变定价或其他条款,或者法规要求在 Google Cloud 未覆盖的地理位置运行软件和存储数据时,可移植性能提供更多选择。
5.2 Knative 服务概述
Knative 服务的主要目标是为开发者提供一个良好的抽象,用于操作基于容器、由请求驱动且能自动从零扩展到多个容器的无状态服务。
Knative 服务与云运行具有相同的资源模型,开发者与之交互的主要资源相同。每次更改服务时,Knative 会创建一个新的不可变版本,版本是容器镜像和运行时配置(如环境变量和资源要求)的组合。其运行时行为也与云运行相同,单个容器是可丢弃的,服务会根据需求自动从零扩展到多个容器。
需要注意的是,云运行不是托管的 Knative 服务,它是对同一规范的兼容但完全独立的专有实现,直接运行在 Google 的超大规模容器基础设施 Borg 之上。
5.3 在 Google Cloud 上使用 Knative 服务
可以在自己的基础设施上运行完全自我管理的 Knative 服务,也可以使用云运行,或者介于两者之间的方案。Knative 提供了一个抽象层,无论是否管理自己的基础设施,开发者都能获得无服务器的开发体验。
6. 理解 Kubernetes
Knative 服务是 Kubernetes 集群的扩展,因此了解 Kubernetes 很有必要。
6.1 Kubernetes 概述
Kubernetes 集群是一组运行容器的节点(服务器),既可以使用供应商提供的托管 Kubernetes 集群,也可以在自己的数据中心的物理硬件上运行并自行管理。
Kubernetes 本质上是一个容器编排器,它会确定在哪个节点上启动容器,并确保网络流量能够到达容器。其架构支持大规模部署,Google 报告称使用 Google Kubernetes Engine(GKE)集群的实际部署可运行多达 15,000 个节点。
以下是 Kubernetes 的主要组件:
| 组件 | 说明 |
| ---- | ---- |
| API 服务器 | 位于 Kubernetes 的中心,通过客户端(如命令行客户端
kubectl
)与之交互。Kubernetes API 基于资源,可创建、读取、更新或删除 Kubernetes 资源。例如,使用
kubectl create deployment --image=docker.io/library/nginx nginx
启动一个带有 Nginx 网络服务器的容器。 |
| Kubernetes 资源 | 如部署(Deployment)和 Pod 等。部署表示一组容器,若容器失败或变得不健康,Kubernetes 会重启它。Pod 是最简单的 Kubernetes 资源,代表集群中的一个或多个协同工作的容器。 |
| 数据库 | 存储所有 Kubernetes 资源,API 服务器是唯一与之连接的组件,负责处理客户端认证、授权和输入验证,并对资源进行更改,但不负责确定启动容器的节点。 |
| 控制器 | 每种资源类型都有一个专用的控制器,当创建、更新或删除资源时,控制器会尝试改变集群以使其符合描述。例如,创建部署资源后,部署控制器会创建其他低级别的 Kubernetes 资源(如 ReplicaSet),最终创建 Pod 并调度到节点上启动。每个节点上的
kubelet
进程会持续监视 API 服务器,一旦有 Pod 被调度到该节点,就会启动容器。 |
6.2 向 Kubernetes 添加扩展
可以使用自定义资源定义(CRD)定义自己的 Kubernetes 资源类型,并告知 API 服务器。然后启动自己的控制器,监视自定义资源的更改并创建其他内置 Kubernetes 资源。
Knative 就是 Kubernetes 的扩展,它添加了新资源,如服务(Service)、配置(Configuration)和版本(Revision),并启动新的控制器来监视这些资源。当有新的 Knative 服务到来时,Knative 控制器会在底层创建和管理 Kubernetes 部署资源。
7. 本地运行 Knative 服务
现在进行实践探索,目标是展示如何将服务从云运行迁移到本地安装的 Knative 服务。不过,不建议使用本地 Kubernetes 集群进行本地开发,可参考使用 Docker 的更好方法。
7.1 运行本地 Kubernetes 集群
运行本地 Kubernetes 集群有多种选择,常见的有 Minikube、k3s 和 kind。Minikube 历史最久,由社区维护,对初学者友好,它使用虚拟机(或 Docker 容器)运行单节点 Kubernetes 集群,便于学习时快速重置。
mermaid 流程图如下:
graph LR
A[开始] --> B[选择本地 Kubernetes 集群方案]
B --> C{方案类型}
C -->|Minikube| D[使用虚拟机或 Docker 容器运行单节点集群]
C -->|k3s| E[其他运行方式]
C -->|kind| F[其他运行方式]
D --> G[学习使用]
E --> G
F --> G
G --> H[结束]
云运行服务的日志记录、跟踪与本地部署实践
8. 本地运行 Knative 服务的实践步骤
虽然前面提到不建议用本地 Kubernetes 集群进行开发,但为了展示云运行与 Knative 服务的兼容性,下面详细介绍本地运行 Knative 服务的步骤。
8.1 安装 Minikube(以 Minikube 为例)
如果你选择 Minikube 来运行本地 Kubernetes 集群,可按以下步骤操作:
1.
下载 Minikube
:根据你的操作系统,从 Minikube 的官方 GitHub 仓库下载对应版本的二进制文件。
2.
安装 Minikube
:将下载的二进制文件添加到系统的
PATH
环境变量中,以便可以在任何位置使用
minikube
命令。
3.
启动 Minikube
:在终端中运行
minikube start
命令,Minikube 会自动下载所需的镜像并启动一个单节点的 Kubernetes 集群。
8.2 安装 Knative Serving
在 Minikube 集群上安装 Knative Serving,步骤如下:
1.
安装 Knative CRDs
:使用
kubectl
命令部署 Knative Serving 的自定义资源定义(CRD)。
kubectl apply -f https://github.com/knative/serving/releases/download/vX.Y.Z/serving-crds.yaml
注意将
vX.Y.Z
替换为你要安装的 Knative Serving 版本号。
2.
安装 Knative Serving 核心组件
:同样使用
kubectl
命令部署核心组件。
kubectl apply -f https://github.com/knative/serving/releases/download/vX.Y.Z/serving-core.yaml
- 等待组件就绪 :使用以下命令检查 Knative Serving 组件是否已成功部署。
kubectl get pods -n knative-serving
确保所有 Pod 都处于
Running
状态。
8.3 部署容器到 Knative Serving
现在可以将一个容器部署到本地的 Knative Serving 中,步骤如下:
1.
创建 Knative 服务定义文件
:例如,创建一个名为
service.yaml
的文件,内容如下:
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: my-service
spec:
template:
spec:
containers:
- image: gcr.io/your-project/your-image:tag
将
gcr.io/your-project/your-image:tag
替换为你实际的容器镜像地址。
2.
部署服务
:使用
kubectl
命令部署服务。
kubectl apply -f service.yaml
- 检查服务状态 :使用以下命令检查服务是否已成功部署。
kubectl get ksvc my-service
确保服务的 URL 已分配,并且状态为
Ready
。
9. 云运行与 Knative 服务的对比总结
| 对比项 | 云运行(Cloud Run) | Knative 服务 |
|---|---|---|
| 部署难度 | 相对简单,无需管理底层基础设施 | 需要一定的 Kubernetes 知识,部署和管理相对复杂 |
| 可移植性 | 依赖 Google Cloud 平台,但与 Knative 规范兼容,可迁移 | 可在不同的 Kubernetes 集群上运行,具有较高的可移植性 |
| 功能特性 | 提供了简化的无服务器体验,功能相对精简 | 功能更丰富,可自定义性强,可根据需求扩展 |
| 成本 | 按使用量计费,成本相对透明 | 成本取决于底层 Kubernetes 集群的配置和使用情况 |
10. 未来展望
随着云计算和容器技术的不断发展,云运行和 Knative 服务都将在无服务器和容器编排领域发挥重要作用。云运行将继续为开发者提供简单、高效的无服务器解决方案,而 Knative 服务作为开源项目,将吸引更多的开发者参与,不断扩展其功能和应用场景。
对于开发者来说,了解云运行和 Knative 服务的特点和使用方法,能够根据项目的需求选择合适的技术方案,提高开发效率和系统的可维护性。同时,掌握分布式跟踪、结构化日志和基于日志的指标等技术,有助于更好地监控和调试应用程序,确保应用的稳定性和性能。
总之,云运行和 Knative 服务为开发者提供了强大的工具和平台,让我们能够更加专注于业务逻辑的实现,而无需过多关注底层基础设施的管理。通过不断学习和实践,我们可以充分发挥这些技术的优势,构建出更加优秀的应用程序。
超级会员免费看
1510

被折叠的 条评论
为什么被折叠?



